Feat: optimize theme package injection (#6822)

* feat: optimize theme package injection

* fix: test case
This commit is contained in:
ClarkXia 2024-03-07 10:04:11 +08:00 committed by GitHub
parent b279c880a1
commit 79c32a77d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 166 additions and 72 deletions

View File

@ -0,0 +1,6 @@
---
'@ice/plugin-fusion': minor
'@ice/style-import': minor
---
feat: optimize function theme injection

View File

@ -32,37 +32,9 @@ function getVariablesPath({
return formatPath(filePath);
}
function importIcon(iconPath: string, cssPrefix: string) {
let entryFile = '';
return {
name: 'transform-import-icon',
enforce: 'pre',
transformInclude(id: string) {
// Only transform source code and icon file.
return (id.match(/\.(js|jsx|ts|tsx)$/) && !id.match(/node_modules/)) || iconPath === formatPath(id);
},
async transform(code: string, id: string, options: { isServer: boolean }) {
const { isServer } = options;
// Only import icon scss in client
if (!isServer) {
// Icon just import once.
if (!entryFile) {
entryFile = id;
}
if (id === entryFile) {
return `import '${iconPath}';\n${code}`;
} else if (formatPath(id) === iconPath) {
// Default cssPrefix for icon.scss.
return `$css-prefix: '${cssPrefix}';\n${code}`;
}
}
},
};
}
const plugin: Plugin<PluginOptions> = (options = {}) => ({
name: '@ice/plugin-fusion',
setup: ({ onGetConfig, createLogger }) => {
setup: ({ onGetConfig, createLogger, generator }) => {
const { theme, themePackage, importStyle } = options;
if (importStyle) {
onGetConfig((config) => {
@ -73,16 +45,18 @@ const plugin: Plugin<PluginOptions> = (options = {}) => ({
});
}
if (theme || themePackage) {
onGetConfig((config) => {
// Try to get icon.scss if exists.
const iconFile = getVariablesPath({
packageName: themePackage,
filename: 'icons.scss',
silent: true,
// Try to get icon.scss if exists.
const iconFile = getVariablesPath({
packageName: themePackage,
filename: 'icons.scss',
silent: true,
});
if (iconFile) {
generator.addEntryImportAhead({
source: iconFile,
});
if (iconFile) {
config.transformPlugins = [...(config.transformPlugins || []), importIcon(iconFile, theme?.['css-prefix'] || 'next-')];
}
}
onGetConfig((config) => {
// Modify webpack config of scss rule for fusion theme.
config.configureWebpack ??= [];
config.configureWebpack.push((webpackConfig) => {
@ -90,8 +64,8 @@ const plugin: Plugin<PluginOptions> = (options = {}) => ({
let sassLoader = null;
rules.some((rule) => {
if (typeof rule === 'object' &&
rule.test instanceof RegExp &&
rule?.test?.source?.match(/scss/)) {
rule.test instanceof RegExp &&
rule?.test?.source?.match(/scss/)) {
sassLoader = Array.isArray(rule?.use) &&
rule.use.find((use) => typeof use === 'object' && use.loader.includes('sass-loader'));
return true;
@ -99,7 +73,9 @@ const plugin: Plugin<PluginOptions> = (options = {}) => ({
return false;
});
if (sassLoader) {
const additionalContent = [];
const additionalContent = [
`$css-prefix: '${theme?.['css-prefix'] || 'next-'}' !default;`,
];
if (themePackage) {
const themeFile = getVariablesPath({
packageName: themePackage,

View File

@ -16,7 +16,7 @@
"url": "https://github.com/alibaba/ice/tree/master/packages/transform-import"
},
"dependencies": {
"es-module-lexer": "^1.0.2",
"rs-module-lexer": "^2.3.0",
"magic-string": "^0.27.0"
},
"publishConfig": {

View File

@ -1,6 +1,8 @@
import { init, parse } from 'es-module-lexer';
import pkg from 'rs-module-lexer';
import MagicString from 'magic-string';
import type { ImportSpecifier, ExportSpecifier } from 'es-module-lexer';
import type { ImportSpecifier, ExportSpecifier } from 'rs-module-lexer';
const { parseAsync, parse } = pkg;
interface TransformOptions {
libraryName: string;
@ -9,7 +11,7 @@ interface TransformOptions {
kebabCase?: Boolean;
}
export async function importStyle(code: string, options: TransformOptions): Promise<null | {
export async function importStyle(code: string, id: string, options: TransformOptions): Promise<null | {
code: string;
map: ReturnType<MagicString['generateMap']>;
}> {
@ -17,18 +19,23 @@ export async function importStyle(code: string, options: TransformOptions): Prom
if (!style) {
return null;
}
await init;
let imports: readonly ImportSpecifier[] = [];
try {
imports = parse(code)[0];
const { output } = await parseAsync({
input: [{
code,
filename: id,
}],
});
imports = output[0].imports;
} catch (e) {
console.log(e);
return null;
}
if (!imports.length) {
return null;
}
let s: MagicString | undefined;
const str = () => s || (s = new MagicString(code));
imports.forEach(({ n, se, ss }) => {
@ -38,9 +45,20 @@ export async function importStyle(code: string, options: TransformOptions): Prom
// Get specifiers by export statement (es-module-lexer can analyze name exported).
if (importStr) {
const exportSource = importStr.replace('import ', 'export ').replace(/\s+as\s+\w+,?/g, ',');
// Namespace export is not supported.
if (exportSource.includes('*')) {
return;
}
let exports: ExportSpecifier[] = [];
try {
exports = parse(exportSource)[1];
const { output } = parse({
input: [{
// Use static filename to mark the source is written by js.
filename: 'export.js',
code: exportSource,
}],
});
exports = output[0].exports;
} catch (e) {
console.log(`error occur when analyze code: ${importStr}`);
console.log(e);
@ -89,7 +107,7 @@ export default function importStylePlugin(options: TransformOptions) {
if (transformOption.isServer || !code) {
return null;
}
return await importStyle(code, options);
return await importStyle(code, id, options);
},
};
}

View File

@ -4,58 +4,59 @@ import { importStyle } from '../src/index';
describe('import style', () => {
it('simple import', async () => {
const sourceCode = 'import { Button } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';`);
});
it('custom style', async () => {
const sourceCode = 'import { Button } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: (name) => `antd/es/${name.toLocaleLowerCase()}/style` });
const result = await importStyle(sourceCode, 'index.ts', { libraryName: 'antd', style: (name) => `antd/es/${name.toLocaleLowerCase()}/style` });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';`);
});
it('mismatch import', async () => {
const sourceCode = 'import { Button } from \'antd-mobile\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}`);
});
it('multiple import', async () => {
const sourceCode = 'import { Button, Table } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';\nimport 'antd/es/table/style';`);
});
it('named import', async () => {
const sourceCode = 'import { Button as Btn } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';`);
});
it('default import', async () => {
const sourceCode = 'import * as antd from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}`);
});
it('sourcemap', async () => {
const sourceCode = 'import * as antd from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true, sourceMap: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true, sourceMap: true });
expect(!!result?.map).toBe(true);
});
it('none import', async () => {
const sourceCode = 'export const a = \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result).toBe(null);
});
it('parse error', async () => {
it('parse export', async () => {
const sourceCode = 'export antd, { Button } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
expect(result).toBe(null);
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';`);
});
it('import error', async () => {
it('mixed import', async () => {
const sourceCode = 'import antd, { Button } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: true });
expect(result?.code).toBe(sourceCode);
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: true });
expect(result?.code).toBe(`${sourceCode}\nimport 'antd/es/button/style';`);
});
it('style false', async () => {
const sourceCode = 'import { Button } from \'antd\';';
const result = await importStyle(sourceCode, { libraryName: 'antd', style: false });
const result = await importStyle(sourceCode, 'index.js', { libraryName: 'antd', style: false });
expect(result).toBe(null);
});
});

View File

@ -2411,12 +2411,12 @@ importers:
packages/style-import:
dependencies:
es-module-lexer:
specifier: ^1.0.2
version: 1.2.0
magic-string:
specifier: ^0.27.0
version: 0.27.0
rs-module-lexer:
specifier: ^2.3.0
version: 2.3.0
packages/webpack-config:
dependencies:
@ -9435,6 +9435,87 @@ packages:
'@webassemblyjs/ast': 1.11.5
'@xtuc/long': 4.2.2
/@xn-sakina/rml-darwin-arm64@2.3.0:
resolution: {integrity: sha512-3CxaA3NRBo6pd9i6Ih5FL+3qmCrYt4nlc1dAw+VhvyUImkSt1tt9WVvm955i2YJVEjQydgsE+U1xhxKJnFa8Hg==}
engines: {node: '>=14'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-darwin-x64@2.3.0:
resolution: {integrity: sha512-law6NEW5kaBnn9Ic2/SzLMlHRQcwYrVhKRKU0H/nC/60DFURe6ZKnhNgJAuS1SyU26oEHH6VR7NOvlSEcVWrEg==}
engines: {node: '>=14'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-linux-arm-gnueabihf@2.3.0:
resolution: {integrity: sha512-/fshahvO2ma/NXbOZQXgJDd6nq7IcvvCP6twTtIr4l7KffJ1DeNdvarrD0Hrco5SAZX2qrq/0Z3RzJtsQ/8INQ==}
engines: {node: '>=14'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-linux-arm64-gnu@2.3.0:
resolution: {integrity: sha512-0/XEPfqvSvFImL+yvTcpNJz12vH5Dit19TSC0scGHL5CrwEOCDwrYrBLMLuPJVqroW7AFM+PRTg6p4GCUdY1Cw==}
engines: {node: '>=14'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-linux-arm64-musl@2.3.0:
resolution: {integrity: sha512-kN9LyssC5HhM36bCWoASB7ECupA3Vpow6kFW6jURyT9p+UXcQzTtkKLQ/2OpRdom/eUNSabYuRmdyxGTJ2XIcw==}
engines: {node: '>=14'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-linux-x64-gnu@2.3.0:
resolution: {integrity: sha512-XFPAqrIHseL8xA4eCNYcTCp6iSst3SnxcPHiKSezHquJdCSm8m0SRzUqJxMPQUULmM2EL5zHtswbbAb7t+AS3g==}
engines: {node: '>=14'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-linux-x64-musl@2.3.0:
resolution: {integrity: sha512-xLbOW1iDTz6fAN5rnGMclRNH1aBagDjrkApWhZ/5wQUaoi5r4ovAJWWAGqxDZlYi2lzpPSzlgKaD4ZlokC7r+A==}
engines: {node: '>=14'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-win32-arm64-msvc@2.3.0:
resolution: {integrity: sha512-gwUV/Kr/Kkg060OBzzhP8ji7kJq1EG3OKhNXqfOlpIQHU4aHq/xKtVEEFu8KlarhgK9oTyjzM+80yMc0XSwtUg==}
engines: {node: '>=14'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@xn-sakina/rml-win32-x64-msvc@2.3.0:
resolution: {integrity: sha512-iJZOAKiMQIVPZ+0QN4sZgo0RewmJu01sSLsHUajc3bTB1EMspZODIRgOli0PjLjWh5WvJ3T230xD9VOaXZ0I8g==}
engines: {node: '>=14'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@xtuc/ieee754@1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@ -12468,10 +12549,6 @@ packages:
resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==}
dev: true
/es-module-lexer@1.2.0:
resolution: {integrity: sha512-2BMfqBDeVCcOlLaL1ZAfp+D868SczNpKArrTM3dhpd7dK/OVlogzY15qpUngt+LMTq5UC/csb9vVQAgupucSbA==}
dev: false
/es-module-lexer@1.2.1:
resolution: {integrity: sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==}
@ -20574,6 +20651,22 @@ packages:
fsevents: 2.3.2
dev: true
/rs-module-lexer@2.3.0:
resolution: {integrity: sha512-Ap6ez0EupuGKoohMq1yClBcGRjivQnemFeR0aIYWjyYT1n8HOywYYi0WUz3bzLVSwAKE4OhFHPDr6LiqHGwwvQ==}
engines: {node: '>=14'}
requiresBuild: true
optionalDependencies:
'@xn-sakina/rml-darwin-arm64': 2.3.0
'@xn-sakina/rml-darwin-x64': 2.3.0
'@xn-sakina/rml-linux-arm-gnueabihf': 2.3.0
'@xn-sakina/rml-linux-arm64-gnu': 2.3.0
'@xn-sakina/rml-linux-arm64-musl': 2.3.0
'@xn-sakina/rml-linux-x64-gnu': 2.3.0
'@xn-sakina/rml-linux-x64-musl': 2.3.0
'@xn-sakina/rml-win32-arm64-msvc': 2.3.0
'@xn-sakina/rml-win32-x64-msvc': 2.3.0
dev: false
/rtl-detect@1.0.4:
resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}