mirror of https://github.com/alibaba/ice.git
Feat: optimize theme package injection (#6822)
* feat: optimize theme package injection * fix: test case
This commit is contained in:
parent
b279c880a1
commit
79c32a77d7
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@ice/plugin-fusion': minor
|
||||
'@ice/style-import': minor
|
||||
---
|
||||
|
||||
feat: optimize function theme injection
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
107
pnpm-lock.yaml
107
pnpm-lock.yaml
|
|
@ -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==}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue