diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9db515d..77e2dc1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,15 +1,12 @@ name: Node.js CI on: push: - branches: [v3, v4] - pull_request: - branches: [v3, v4] jobs: build: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x, 16.x, 18.x, 19.x] + node-version: [16.x, 18.x, 19.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index 799453b..c860a7e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ node_modules # Build files dist /test/**/output.* +/test/**/output/ \ No newline at end of file diff --git a/package.json b/package.json index 17689c5..16cd5a0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,8 @@ { - "name": "rollup-plugin-css-only", + "name": "@sgratzl/rollup-plugin-css-only", + "publishConfig": { + "access": "public" + }, "version": "4.3.0", "description": "Rollup plugin that bundles imported css", "main": "dist/index.cjs", @@ -18,8 +21,9 @@ "test:nested": "cd test/nested && rm -rf output && rollup -c && cmp output/output.js expected.js && cmp output/output.css expected.css && cd ../..", "test:empty": "cd test/empty && rm -rf output && rollup -c && cmp output/output.js expected.js && cmp output/output.css expected.css && cd ../..", "test:simple": "cd test/simple && rm -rf output && rollup -c && cmp output/output.js expected.js && cmp output/output.css expected.css && cd ../..", + "test:imports": "cd test/imports && rm -rf output && rollup -c && cmp output/output.js expected.js && cmp output/output.css expected.css && cd ../..", "test:win:simple": "cd .\\test\\simple && del -f output.* && rollup -c && cd .. && ECHO n|comp simple\\output.js expected.js && ECHO n|comp simple\\output.css simple\\expected.css && cd ..", - "test": "npm run test:simple && npm run test:nested && npm run test:empty && npm run test:circular", + "test": "npm run test:simple && npm run test:nested && npm run test:empty && npm run test:circular && npm run test:imports", "test:win": "npm run test:win:simple", "lint": "prettier rollup.config.js src/**", "prepare": "npm run build", @@ -34,13 +38,16 @@ ], "license": "MIT", "author": "Thomas Ghysels ", - "homepage": "https://github.com/thgh/rollup-plugin-css-only", + "contributors": [ + "Samuel Gratzl " + ], + "homepage": "https://github.com/sgratzl/rollup-plugin-css-only", "bugs": { - "url": "https://github.com/thgh/rollup-plugin-css-only/issues" + "url": "https://github.com/sgratzl/rollup-plugin-css-only/issues" }, "repository": { "type": "git", - "url": "https://github.com/thgh/rollup-plugin-css-only" + "url": "https://github.com/sgratzl/rollup-plugin-css-only" }, "files": [ "dist" diff --git a/src/index.mjs b/src/index.mjs index 6fba44f..ca36bf5 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -8,30 +8,38 @@ export default function css(options = {}) { let fileName = options.fileName // Get all CSS modules in the order that they were imported - const getCSSModules = (id, getModuleInfo, modules = new Set(), visitedModules = new Set()) => { - if (modules.has(id) || visitedModules.has(id)) { - return new Set() + const getCSSModules = (id, getModuleInfo) => { + const modules = []; + const visited = new Set(); + + // traversal logic + // 1. mark node as visited + // 2. add to list at the end + // 3. go down with imports but in reverse order + // 4. reverse full list + // example + // root + // 1 + // 11 + // 12 + // 2 + // 21 + // 22 + // will result in the list: root, 2, 22, 21, 1, 12, 11 + // revered: 11, 12, 1, 21, 22, 2, root + const visitModule = (id) => { + if (visited.has(id)) { + return; + } + visited.add(id); + if (filter(id)) { + modules.push(id); + } + const reverseChildren = getModuleInfo(id).importedIds.slice().reverse(); + reverseChildren.forEach(visitModule); } - - if (filter(id)) modules.add(id) - - // Prevent infinite recursion with circular dependencies - visitedModules.add(id); - - // Recursively retrieve all of imported CSS modules - const info = getModuleInfo(id) - if (!info) return modules - - info.importedIds.forEach(importId => { - modules = new Set( - [].concat( - Array.from(modules), - Array.from(getCSSModules(importId, getModuleInfo, modules, visitedModules)) - ) - ) - }) - - return modules + visitModule(id); + return modules.reverse(); } return { @@ -41,10 +49,21 @@ export default function css(options = {}) { return } + const { imports, codeWithoutImports } = splitImports(code); + // When output is disabled, the stylesheet is exported as a string if (options.output === false) { + if (imports.length === 0) { + return { + code: `export default ${JSON.stringify(code)}`, + map: { mappings: '' } + } + } + const importNamed = imports.map((d, i) => `import i${i} from ${d}`).join('\n'); return { - code: 'export default ' + JSON.stringify(code), + code: ` + ${importNamed} + export default ${imports.map((_, i) => `i${i}`).join(' + ')} + ${JSON.stringify(codeWithoutImports)}`, map: { mappings: '' } } } @@ -52,11 +71,12 @@ export default function css(options = {}) { // Keep track of every stylesheet // Check if it changed since last render // NOTE: If we are in transform block, we can assume styles[id] !== code, right? - if (styles[id] !== code && (styles[id] || code)) { - styles[id] = code + if (styles[id] !== codeWithoutImports && (styles[id] || codeWithoutImports)) { + styles[id] = codeWithoutImports } - return '' + // return a list of imports + return imports.map((d) => `import ${d}`).join('\n'); }, generateBundle(opts, bundle) { const ids = [] @@ -86,3 +106,16 @@ export default function css(options = {}) { } } } + + +function splitImports(code) { + const imports = []; + const codeWithoutImports = code.replace(/@import\s+(.*);[\r\n]*/gm, (_, group) => { + imports.push(group.replace(/(["'])~/, '$1')); + return ''; + }); + return { + imports, + codeWithoutImports + }; +} \ No newline at end of file diff --git a/test/circular/rollup.config.mjs b/test/circular/rollup.config.mjs index 5e80f51..7fd218e 100644 --- a/test/circular/rollup.config.mjs +++ b/test/circular/rollup.config.mjs @@ -1,4 +1,4 @@ -import css from 'rollup-plugin-css-only' +import css from '../../src/index.mjs' export default { input: 'a.js', diff --git a/test/imports/css/first.css b/test/imports/css/first.css new file mode 100644 index 0000000..33185d6 --- /dev/null +++ b/test/imports/css/first.css @@ -0,0 +1,3 @@ +.first { + color: blue; +} diff --git a/test/imports/css/input.css b/test/imports/css/input.css new file mode 100644 index 0000000..1d21b15 --- /dev/null +++ b/test/imports/css/input.css @@ -0,0 +1,5 @@ +@import './second.css'; + +.last { + color: green; +} diff --git a/test/imports/css/second.css b/test/imports/css/second.css new file mode 100644 index 0000000..8a4679f --- /dev/null +++ b/test/imports/css/second.css @@ -0,0 +1,5 @@ +@import './first.css'; + +.second { + color: green; +} diff --git a/test/imports/expected.css b/test/imports/expected.css new file mode 100644 index 0000000..7af10cd --- /dev/null +++ b/test/imports/expected.css @@ -0,0 +1,12 @@ +.first { + color: blue; +} + +.second { + color: green; +} + +.last { + color: green; +} + diff --git a/test/imports/expected.js b/test/imports/expected.js new file mode 100644 index 0000000..dddc8cf --- /dev/null +++ b/test/imports/expected.js @@ -0,0 +1 @@ +console.log('css imported'); diff --git a/test/imports/input.js b/test/imports/input.js new file mode 100644 index 0000000..459af57 --- /dev/null +++ b/test/imports/input.js @@ -0,0 +1,3 @@ +import './css/input.css' + +console.log('css imported') diff --git a/test/imports/rollup.config.mjs b/test/imports/rollup.config.mjs new file mode 100644 index 0000000..2b1ef9e --- /dev/null +++ b/test/imports/rollup.config.mjs @@ -0,0 +1,12 @@ +import css from '../../src/index.mjs' + +export default { + input: 'input.js', + output: { + file: 'output/output.js', + format: 'esm' + }, + plugins: [ + css({ output: 'output.css' }) + ] +} \ No newline at end of file