@lynx-js/template-webpack-plugin
Version:
Simplifies creation of Lynx template files to serve your webpack bundles
92 lines • 4.43 kB
JavaScript
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import * as cssTree from 'css-tree';
// `COMMON_CSS` is the global styles that applies to all the elements.
// It should always has `cssId: 0`.
const COMMON_CSS = '/common.css';
const COMMON_CSS_ID = 0;
export function debundleCSS(code, css, enableCSSSelector) {
const ast = cssTree.parse(code);
const fileKeyToCSSContent = new Map();
const cssIdToFileKeys = new Map();
const fileKeyToCSSId = new Map([[COMMON_CSS, COMMON_CSS_ID]]);
cssTree.walk(ast, {
visit: 'Atrule',
enter(node, item, list) {
if (node.type === 'Atrule' && node.prelude
&& node.prelude.type === 'AtrulePrelude' // Minify/Format will change `cssId` to `cssid`.
&& node.name.toLowerCase() === 'cssId'.toLowerCase()) {
// @cssId "842372" "foo.css" {}
const [cssIdNode, fileKeyNode] = node.prelude.children.toArray()
.filter(({ type }) => type !== 'WhiteSpace');
if (cssIdNode?.type === 'String'
&& fileKeyNode?.type === 'String'
&& node.block) {
const cssId = Number(cssIdNode.value);
if (Number.isNaN(cssId)) {
throw new Error(`Invalid cssId: @cssId "${cssIdNode.value}" "${fileKeyNode.value}"`);
}
const fileKey = fileKeyNode.value;
let fileKeys = cssIdToFileKeys.get(cssId);
if (typeof fileKeys === 'undefined') {
// Every file should import COMMON_CSS(cssId: 0).
// Otherwise, the global styles cannot be resolved by elements.
fileKeys = new Set([COMMON_CSS]);
cssIdToFileKeys.set(cssId, fileKeys);
}
fileKeys.add(fileKey);
if (!fileKeyToCSSContent.has(fileKey)) {
fileKeyToCSSContent.set(fileKey, cssTree.generate({
type: 'StyleSheet',
children: node.block.children,
}));
}
}
list.remove(item);
}
},
});
// If there are Rules left in the AST(e.g.: some rules that are not in `@file {}`),
// we treat them as global styles. Global styles should be added to COMMON_CSS(cssId: 0).
const commonCss = cssTree.generate(ast);
if (commonCss) {
emplaceCSSStyleSheet(css, COMMON_CSS_ID, commonCss);
}
// TODO: resolve conflict with scoped cssId
// E.g.: we may generate a cssId hash that is equal to the index of source files.
//
// For each CSS source file, we create a CSSStyleSheet.
// The scoped CSSStyleSheet will use `@import` to reference all the imported CSSStyleSheets.
//
// Note that the `Map.prototype.keys()` returns an iterator in insertion order.
// This will make sure that the stylesheets are created in the same order of CSS.
Array.from(fileKeyToCSSContent.keys()).forEach((fileKey, index) => {
// Starts from 1
// 0 is the common CSS
index = index + 1;
fileKeyToCSSId.set(fileKey, index);
emplaceCSSStyleSheet(css, index, fileKeyToCSSContent.get(fileKey));
});
// TODO: remove /cssId/0.css if not exists in the cssMap
// For each scoped CSSStyleSheet, we should import the real CSSStyleSheet.
// So that the styles can be resolved with the scoped cssId.
cssIdToFileKeys.forEach((rawFileKeys, cssId) => {
let fileKeys = Array.from(rawFileKeys);
if (enableCSSSelector === false) {
// When enableCSSSelector is false, style rule priority is inversely related to @import order,
// requiring reversed imports to maintain correct priority.
fileKeys = fileKeys.reverse();
}
emplaceCSSStyleSheet(css, cssId, Array.from(fileKeys).map(fileKey => `@import "${fileKeyToCSSId.get(fileKey)}";`).join('\n'));
});
}
function emplaceCSSStyleSheet(map, key, value) {
if (map.has(key)) {
map.get(key).push(value);
}
else {
map.set(key, [value]);
}
}
//# sourceMappingURL=debundle.js.map