Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS imports #31

Open
wants to merge 17 commits into
base: v4
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ node_modules
# Build files
dist
/test/**/output.*
/test/**/output/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dev": "rollup -cw",
"test:nested": "cd test/nested && rm -rf output && rollup -c && cmp output/bundle.js expected/bundle.js && cmp output/bundle.css expected/bundle.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": "npm run test:simple && npm run test:nested",
"test:imports": "cd test/imports && rm -rf output && rollup -c && cmp output/bundle.js expected/bundle.js && cmp output/bundle.css expected/bundle.css && cd ../..",
"test": "npm run test:simple && npm run test:nested && npm run test:imports",
"lint": "prettier rollup.config.js src/**",
"prepare": "npm run build",
"prepublish": "npm run build"
Expand Down
102 changes: 70 additions & 32 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
import { createFilter } from '@rollup/pluginutils'

var arraysEqual = function(a, b) {
if (a.length !== b.length) return false

for (let i = a.length; i--;) {
if (a[i] !== b[i]) return false
}

return true
}

export default function css(options = {}) {
const filter = createFilter(options.include || ['**/*.css'], options.exclude)
const styles = {}
let dest = options.output
let hasChanged = false
let prevIds = []

// Get all CSS modules in the order that they were imported
const getCSSModules = (id, getModuleInfo, modules = new Set()) => {
if (modules.has(id)) {
return new Set()
}

if (filter(id)) modules.add(id)

// Recursively retrieve all of imported CSS modules
getModuleInfo(id).importedIds.forEach(importId => {
modules = new Set([].concat(Array.from(modules), Array.from(getCSSModules(importId, getModuleInfo, modules))))
});

return modules
};

return {
name: 'css',
buildStart() {
Expand All @@ -43,32 +17,44 @@ 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: '' }
}
}

// 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?
thgh marked this conversation as resolved.
Show resolved Hide resolved
if (styles[id] !== code && (styles[id] || code)) {
styles[id] = code
if (styles[id] !== codeWithoutImports && (styles[id] || codeWithoutImports)) {
styles[id] = codeWithoutImports
hasChanged = true
}

return ''
// return a list of imports
return imports.map((d) => `import ${d}`).join('\n');
},
generateBundle(opts, bundle) {
const ids = []

// Determine import order of files
for (const file in bundle) {
const root = bundle[file].facadeModuleId
const modules = getCSSModules(root, this.getModuleInfo)
ids.push(...Array.from(modules))
const modules = getCSSModules(root, filter, this.getModuleInfo)
ids.push(...modules)
}

// If the files are imported in the same order and there are no changes
Expand Down Expand Up @@ -115,3 +101,55 @@ export default function css(options = {}) {
}
}
}

function arraysEqual(a, b) {
if (a.length !== b.length) return false
return a.every((ai, i) => ai === b[i]);
}

function splitImports(code) {
const imports = [];
const codeWithoutImports = code.replace(/@import\s+(.*);[\r\n]*/gm, (_, group) => {
imports.push(group.replace(/(["'])~/, '$1'));
return '';
});
return {
imports,
codeWithoutImports
};
}

// Get all CSS modules in the order that they were imported
function getCSSModules(id, filter, 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);
}
visitModule(id);
return modules.reverse();
};
3 changes: 3 additions & 0 deletions test/imports/css/first.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.first {
color: blue;
}
5 changes: 5 additions & 0 deletions test/imports/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './second.css';

.last {
color: green;
}
5 changes: 5 additions & 0 deletions test/imports/css/second.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './first.css';

.second {
color: green;
}
12 changes: 12 additions & 0 deletions test/imports/expected/bundle.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.first {
color: blue;
}

.second {
color: green;
}

.last {
color: green;
}

1 change: 1 addition & 0 deletions test/imports/expected/bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('css imported');
3 changes: 3 additions & 0 deletions test/imports/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './css/input.css'

console.log('css imported')
12 changes: 12 additions & 0 deletions test/imports/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import css from '../../src/index.js'

export default {
input: 'input.js',
output: {
file: 'output/bundle.js',
format: 'esm'
},
plugins: [
css({ output: 'bundle.css' })
]
}