feat: optimize output folder (#115)

* feat: optimize folder

* fix: assets transform

* fix: optimze code

* fix: optimize code
This commit is contained in:
ClarkXia 2022-04-14 20:06:14 +08:00
parent 54163342ef
commit b50d478103
16 changed files with 111 additions and 38 deletions

View File

@ -1,11 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import { Link } from 'ice'; import { Link } from 'ice';
// @ts-expect-error
import url from './ice.png';
export default function About() { export default function About() {
return ( return (
<> <>
<h2>About Page</h2> <h2>About Page</h2>
<Link to="/">home</Link> <Link to="/">home</Link>
<img src={url} height="40" width="40" />
<span className="mark">new</span> <span className="mark">new</span>
</> </>
); );

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -34,7 +34,7 @@
"devDependencies": { "devDependencies": {
"@ice/types": "^1.0.0", "@ice/types": "^1.0.0",
"build-scripts": "^2.0.0-16", "build-scripts": "^2.0.0-16",
"webpack": "^5.69.1", "webpack": "^5.72.0",
"webpack-dev-server": "^4.7.4" "webpack-dev-server": "^4.7.4"
} }
} }

View File

@ -1,19 +1,23 @@
import type { ModifyWebpackConfig } from '@ice/types/esm/config'; import type { ModifyWebpackConfig } from '@ice/types/esm/config';
type AssetRuleConfig = [RegExp, Record<string, any>?]; type AssetRuleConfig = [RegExp, Record<string, any>?, boolean?];
function configAssetsRule(config: AssetRuleConfig) { function configAssetsRule(config: AssetRuleConfig) {
const [test, dataUrl] = config; const [test, dataUrl = {}, inlineLimit = true] = config;
return { return {
test, test,
type: 'asset',
parser: {
...(inlineLimit ? {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
} : {}),
},
generator: { generator: {
dataUrl, dataUrl,
}, },
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
},
}; };
} }
@ -22,19 +26,9 @@ const assets: ModifyWebpackConfig = (config) => {
[/\.woff2?$/, { mimetype: 'application/font-woff' }], [/\.woff2?$/, { mimetype: 'application/font-woff' }],
[/\.ttf$/, { mimetype: 'application/octet-stream' }], [/\.ttf$/, { mimetype: 'application/octet-stream' }],
[/\.eot$/, { mimetype: 'application/vnd.ms-fontobject' }], [/\.eot$/, { mimetype: 'application/vnd.ms-fontobject' }],
[/\.svg$/, { mimetype: 'image/svg+xml' }], [/\.svg$/, { mimetype: 'image/svg+xml' }, false],
[/\.(png|jpg|webp|jpeg|gif)$/i], [/\.(png|jpg|webp|jpeg|gif)$/i],
] as AssetRuleConfig[]).map((config) => configAssetsRule(config)); ] as AssetRuleConfig[]).map((config) => configAssetsRule(config));
config.module.parser = {
javascript: {
url: 'relative',
},
};
config.module.generator = {
asset: {
filename: 'assets/[name].[hash:8][ext]',
},
};
config.module.rules.push(...assetsRule); config.module.rules.push(...assetsRule);
return config; return config;
}; };

View File

@ -84,7 +84,7 @@ function configCSSRule(config: CSSRuleConfig, options: Options) {
const css: ModifyWebpackConfig = (config, ctx) => { const css: ModifyWebpackConfig = (config, ctx) => {
const { supportedBrowsers, publicPath, hashKey } = ctx; const { supportedBrowsers, publicPath, hashKey } = ctx;
const cssOutputFolder = 'css';
config.module.rules.push(...([ config.module.rules.push(...([
['css'], ['css'],
['less', require.resolve('@builder/pack/deps/less-loader'), ({ lessOptions: { javascriptEnabled: true } })], ['less', require.resolve('@builder/pack/deps/less-loader'), ({ lessOptions: { javascriptEnabled: true } })],
@ -92,7 +92,7 @@ const css: ModifyWebpackConfig = (config, ctx) => {
] as CSSRuleConfig[]).map((config) => configCSSRule(config, { publicPath, browsers: supportedBrowsers }))); ] as CSSRuleConfig[]).map((config) => configCSSRule(config, { publicPath, browsers: supportedBrowsers })));
config.plugins.push( config.plugins.push(
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: hashKey ? `[name]-[${hashKey}].css` : '[name].css', filename: `${cssOutputFolder}/${hashKey ? `[name]-[${hashKey}].css` : '[name].css'}`,
// If the warning is triggered, it seen to be unactionable for the user, // If the warning is triggered, it seen to be unactionable for the user,
ignoreOrder: true, ignoreOrder: true,
}), }),

View File

@ -143,7 +143,8 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config, commandArgs = {}
output: { output: {
publicPath, publicPath,
path: path.isAbsolute(outputDir) ? outputDir : path.join(rootDir, outputDir), path: path.isAbsolute(outputDir) ? outputDir : path.join(rootDir, outputDir),
filename: hashKey ? `[name]-[${hashKey}].js` : '[name].js', filename: `js/${hashKey ? `[name]-[${hashKey}].js` : '[name].js'}`,
assetModuleFilename: 'assets/[name].[hash:8][ext]',
}, },
context: rootDir, context: rootDir,
module: { module: {
@ -160,7 +161,8 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config, commandArgs = {}
}, },
}, },
watchOptions: { watchOptions: {
// add a delay before rebuilding once routes changed webpack can not found routes component after it is been deleted // add a delay before rebuilding once routes changed
// webpack can not found routes component after it is been deleted
aggregateTimeout: 200, aggregateTimeout: 200,
ignored: watchIgnoredRegexp, ignored: watchIgnoredRegexp,
}, },

View File

@ -25,8 +25,12 @@ const compilationPlugin = (options: Options): UnpluginOptions => {
const compileRegex = compileIncludes.map((includeRule) => { const compileRegex = compileIncludes.map((includeRule) => {
return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule); return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule);
}); });
const extensionRegex = /\.(jsx?|tsx?|mjs)$/;
return { return {
name: 'compilation-plugin', name: 'compilation-plugin',
transformInclude(id) {
return extensionRegex.test(id);
},
// @ts-expect-error TODO: source map types // @ts-expect-error TODO: source map types
async transform(source: string, id: string) { async transform(source: string, id: string) {
if ((/node_modules/.test(id) && !compileRegex.some((regex) => regex.test(id)))) { if ((/node_modules/.test(id) && !compileRegex.some((regex) => regex.test(id)))) {
@ -34,10 +38,6 @@ const compilationPlugin = (options: Options): UnpluginOptions => {
} }
const suffix = (['jsx', 'tsx'] as JSXSuffix[]).find(suffix => new RegExp(`\\.${suffix}?$`).test(id)); const suffix = (['jsx', 'tsx'] as JSXSuffix[]).find(suffix => new RegExp(`\\.${suffix}?$`).test(id));
if (!suffix) {
return;
}
const programmaticOptions = { const programmaticOptions = {
filename: id, filename: id,
sourceMaps: !!sourceMap, sourceMaps: !!sourceMap,

View File

@ -29,8 +29,16 @@ export default class AssetsManifestPlugin {
public createAssets(compilation: Compilation) { public createAssets(compilation: Compilation) {
const entries = {}; const entries = {};
const pages = {}; const pages = {};
const assets = {};
const entrypoints = compilation.entrypoints.values(); const entrypoints = compilation.entrypoints.values();
const assetsInfo = compilation.assetsInfo.values();
for (const asset of assetsInfo) {
if (asset.sourceFilename) {
assets[asset.sourceFilename] = asset.contenthash;
}
}
for (const entrypoint of entrypoints) { for (const entrypoint of entrypoints) {
const entryName = entrypoint.name; const entryName = entrypoint.name;
@ -50,6 +58,7 @@ export default class AssetsManifestPlugin {
publicPath: compilation.outputOptions?.publicPath, publicPath: compilation.outputOptions?.publicPath,
entries, entries,
pages, pages,
assets,
}; };
const manifestFileName = resolve(this.outputDir, this.fileName); const manifestFileName = resolve(this.outputDir, this.fileName);

View File

@ -38,11 +38,12 @@
"postcss": "^8.4.7", "postcss": "^8.4.7",
"postcss-modules": "^4.3.1", "postcss-modules": "^4.3.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"mrmime": "^1.0.0",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"sass": "^1.49.9", "sass": "^1.49.9",
"semver": "^7.3.5", "semver": "^7.3.5",
"webpack": "^5.72.0",
"temp": "^0.9.4", "temp": "^0.9.4",
"webpack": "^5.69.1",
"webpack-dev-server": "^4.7.4" "webpack-dev-server": "^4.7.4"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,54 @@
import * as path from 'path';
import * as mrmime from 'mrmime';
import fs from 'fs-extra';
const ASSET_TYPES = [
// images
'png',
'jpe?g',
'gif',
'svg',
'webp',
// fonts
'woff2?',
'eot',
'ttf',
];
const ASSETS_RE = new RegExp(`\\.(${ASSET_TYPES.join('|')})(\\?.*)?$`);
interface AssetsManifest {
publicPath: string;
assets?: {
[assetPath: string]: string;
};
}
const createAssetsPlugin = (manifestPath: string, rootDir: string) => ({
name: 'esbuild-assets',
setup(build) {
const assetsManifest: AssetsManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
build.onLoad({ filter: ASSETS_RE }, async (args) => {
const relativePath = path.relative(rootDir, args.path);
let content = await fs.promises.readFile(args.path);
let url = '';
const contentHash = assetsManifest!.assets[relativePath];
if (contentHash) {
const basename = path.basename(args.path);
const extname = path.extname(basename);
const ext = extname.substring(1);
const name = basename.slice(0, -extname.length);
// assets/[name].[hash:8][ext]
url = `${assetsManifest.publicPath}assets/${name}.${contentHash}.${ext}`;
} else {
url = `data:${mrmime.lookup(args.path)};base64,${content.toString('base64')}`;
}
return {
contents: `export default ${JSON.stringify(url)}`,
loader: 'js',
};
});
},
});
export default createAssetsPlugin;

View File

@ -1,5 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import type { Plugin } from '@ice/types'; import type { Plugin } from '@ice/types';
import createAssetsPlugin from '../../esbuild/assets.js';
import generateHTML from './ssr/generateHTML.js'; import generateHTML from './ssr/generateHTML.js';
import { setupRenderServer } from './ssr/serverRender.js'; import { setupRenderServer } from './ssr/serverRender.js';
@ -8,16 +9,20 @@ const webPlugin: Plugin = ({ registerTask, context, onHook }) => {
const { ssg = true, ssr = true } = userConfig; const { ssg = true, ssr = true } = userConfig;
const outputDir = path.join(rootDir, 'build'); const outputDir = path.join(rootDir, 'build');
const routeManifest = path.join(rootDir, '.ice/route-manifest.json'); const routeManifest = path.join(rootDir, '.ice/route-manifest.json');
const serverEntry = path.join(outputDir, 'server/entry.mjs'); const assetsManifest = path.join(rootDir, '.ice/assets-manifest.json');
const serverEntry = path.join(outputDir, 'server/index.mjs');
let serverCompiler = async () => ''; let serverCompiler = async () => '';
onHook(`before.${command as 'start' | 'build'}.run`, async ({ esbuildCompile }) => { onHook(`before.${command as 'start' | 'build'}.run`, async ({ esbuildCompile }) => {
serverCompiler = async () => { serverCompiler = async () => {
await esbuildCompile({ await esbuildCompile({
entryPoints: [path.join(rootDir, '.ice/entry.server')], entryPoints: [path.join(rootDir, '.ice/entry.server')],
outdir: path.join(outputDir, 'server'), outfile: serverEntry,
// platform: 'node', // platform: 'node',
format: 'esm', format: 'esm',
outExtension: { '.js': '.mjs' }, outExtension: { '.js': '.mjs' },
plugins: [
createAssetsPlugin(assetsManifest, rootDir),
],
}); });
// timestamp for disable import cache // timestamp for disable import cache
return `${serverEntry}?version=${new Date().getTime()}`; return `${serverEntry}?version=${new Date().getTime()}`;

View File

@ -51,6 +51,7 @@ export function createEsbuildCompiler(options: Options) {
return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule); return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule);
}), }),
}), }),
...(buildOptions.plugins || []),
...transformPlugins ...transformPlugins
// ignore compilation-plugin while esbuild has it's own transform // ignore compilation-plugin while esbuild has it's own transform
.filter(({ name }) => name !== 'compilation-plugin') .filter(({ name }) => name !== 'compilation-plugin')

View File

@ -85,6 +85,9 @@ export interface AssetsManifest {
publicPath: string; publicPath: string;
entries: string[]; entries: string[];
pages: string[]; pages: string[];
assets?: {
[assetPath: string]: string;
};
} }
export interface AppContext { export interface AppContext {
appConfig: AppConfig; appConfig: AppConfig;

View File

@ -32,7 +32,7 @@
"@ice/route-manifest": "^1.0.0", "@ice/route-manifest": "^1.0.0",
"react": "^17.0.2", "react": "^17.0.2",
"unplugin": "^0.3.2", "unplugin": "^0.3.2",
"webpack": "^5.69.1", "webpack": "^5.72.0",
"webpack-dev-server": "^4.7.4" "webpack-dev-server": "^4.7.4"
} }
} }

View File

@ -134,7 +134,7 @@ importers:
regenerator-runtime: ^0.13.9 regenerator-runtime: ^0.13.9
sass: ^1.49.9 sass: ^1.49.9
unplugin: ^0.3.2 unplugin: ^0.3.2
webpack: ^5.69.1 webpack: ^5.72.0
webpack-dev-server: ^4.7.4 webpack-dev-server: ^4.7.4
dependencies: dependencies:
'@builder/pack': 0.6.1 '@builder/pack': 0.6.1
@ -188,6 +188,7 @@ importers:
fs-extra: ^10.0.0 fs-extra: ^10.0.0
less: ^4.1.2 less: ^4.1.2
lodash.merge: ^4.6.2 lodash.merge: ^4.6.2
mrmime: ^1.0.0
postcss: ^8.4.7 postcss: ^8.4.7
postcss-modules: ^4.3.1 postcss-modules: ^4.3.1
prettier: ^2.5.1 prettier: ^2.5.1
@ -195,7 +196,7 @@ importers:
semver: ^7.3.5 semver: ^7.3.5
temp: ^0.9.4 temp: ^0.9.4
unplugin: ^0.3.2 unplugin: ^0.3.2
webpack: ^5.69.1 webpack: ^5.72.0
webpack-dev-server: ^4.7.4 webpack-dev-server: ^4.7.4
dependencies: dependencies:
'@builder/pack': 0.6.1 '@builder/pack': 0.6.1
@ -215,6 +216,7 @@ importers:
fs-extra: 10.0.1 fs-extra: 10.0.1
less: 4.1.2 less: 4.1.2
lodash.merge: 4.6.2 lodash.merge: 4.6.2
mrmime: 1.0.0
postcss: 8.4.12 postcss: 8.4.12
postcss-modules: 4.3.1_postcss@8.4.12 postcss-modules: 4.3.1_postcss@8.4.12
prettier: 2.6.2 prettier: 2.6.2
@ -269,7 +271,7 @@ importers:
esbuild: ^0.14.23 esbuild: ^0.14.23
react: ^17.0.2 react: ^17.0.2
unplugin: ^0.3.2 unplugin: ^0.3.2
webpack: ^5.69.1 webpack: ^5.72.0
webpack-dev-server: ^4.7.4 webpack-dev-server: ^4.7.4
devDependencies: devDependencies:
'@ice/route-manifest': link:../route-manifest '@ice/route-manifest': link:../route-manifest
@ -4599,7 +4601,6 @@ packages:
/mrmime/1.0.0: /mrmime/1.0.0:
resolution: {integrity: sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==} resolution: {integrity: sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/ms/2.0.0: /ms/2.0.0:
resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=}
@ -6501,7 +6502,7 @@ packages:
mime-types: 2.1.35 mime-types: 2.1.35
range-parser: 1.2.1 range-parser: 1.2.1
schema-utils: 4.0.0 schema-utils: 4.0.0
webpack: 5.72.0_esbuild@0.14.36 webpack: 5.72.0_@swc+core@1.2.165
/webpack-dev-server/4.8.1_webpack@5.72.0: /webpack-dev-server/4.8.1_webpack@5.72.0:
resolution: {integrity: sha512-dwld70gkgNJa33czmcj/PlKY/nOy/BimbrgZRaR9vDATBQAYgLzggR0nxDtPLJiLrMgZwbE6RRfJ5vnBBasTyg==} resolution: {integrity: sha512-dwld70gkgNJa33czmcj/PlKY/nOy/BimbrgZRaR9vDATBQAYgLzggR0nxDtPLJiLrMgZwbE6RRfJ5vnBBasTyg==}
@ -6541,7 +6542,7 @@ packages:
serve-index: 1.9.1 serve-index: 1.9.1
sockjs: 0.3.24 sockjs: 0.3.24
spdy: 4.0.2 spdy: 4.0.2
webpack: 5.72.0_esbuild@0.14.36 webpack: 5.72.0_@swc+core@1.2.165
webpack-dev-middleware: 5.3.1_webpack@5.72.0 webpack-dev-middleware: 5.3.1_webpack@5.72.0
ws: 8.5.0 ws: 8.5.0
transitivePeerDependencies: transitivePeerDependencies:

View File

@ -20,7 +20,7 @@ describe(`build ${example}`, () => {
page = res.page; page = res.page;
browser = res.browser; browser = res.browser;
expect(await page.$$text('h2')).toStrictEqual(['Home Page']); expect(await page.$$text('h2')).toStrictEqual(['Home Page']);
const bundleContent = fs.readFileSync(path.join(__dirname, `../../examples/${example}/build/index.js`), 'utf-8'); const bundleContent = fs.readFileSync(path.join(__dirname, `../../examples/${example}/build/js/index.js`), 'utf-8');
expect(bundleContent.includes('__REMOVED__')).toBe(false); expect(bundleContent.includes('__REMOVED__')).toBe(false);
}, 120000); }, 120000);