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 { Link } from 'ice';
// @ts-expect-error
import url from './ice.png';
export default function About() {
return (
<>
<h2>About Page</h2>
<Link to="/">home</Link>
<img src={url} height="40" width="40" />
<span className="mark">new</span>
</>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

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

View File

@ -1,19 +1,23 @@
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) {
const [test, dataUrl] = config;
const [test, dataUrl = {}, inlineLimit = true] = config;
return {
test,
type: 'asset',
parser: {
...(inlineLimit ? {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
} : {}),
},
generator: {
dataUrl,
},
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
},
};
}
@ -22,19 +26,9 @@ const assets: ModifyWebpackConfig = (config) => {
[/\.woff2?$/, { mimetype: 'application/font-woff' }],
[/\.ttf$/, { mimetype: 'application/octet-stream' }],
[/\.eot$/, { mimetype: 'application/vnd.ms-fontobject' }],
[/\.svg$/, { mimetype: 'image/svg+xml' }],
[/\.svg$/, { mimetype: 'image/svg+xml' }, false],
[/\.(png|jpg|webp|jpeg|gif)$/i],
] 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);
return config;
};

View File

@ -84,7 +84,7 @@ function configCSSRule(config: CSSRuleConfig, options: Options) {
const css: ModifyWebpackConfig = (config, ctx) => {
const { supportedBrowsers, publicPath, hashKey } = ctx;
const cssOutputFolder = 'css';
config.module.rules.push(...([
['css'],
['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 })));
config.plugins.push(
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,
ignoreOrder: true,
}),

View File

@ -143,7 +143,8 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config, commandArgs = {}
output: {
publicPath,
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,
module: {
@ -160,7 +161,8 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config, commandArgs = {}
},
},
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,
ignored: watchIgnoredRegexp,
},

View File

@ -25,8 +25,12 @@ const compilationPlugin = (options: Options): UnpluginOptions => {
const compileRegex = compileIncludes.map((includeRule) => {
return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule);
});
const extensionRegex = /\.(jsx?|tsx?|mjs)$/;
return {
name: 'compilation-plugin',
transformInclude(id) {
return extensionRegex.test(id);
},
// @ts-expect-error TODO: source map types
async transform(source: string, id: string) {
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));
if (!suffix) {
return;
}
const programmaticOptions = {
filename: id,
sourceMaps: !!sourceMap,

View File

@ -29,8 +29,16 @@ export default class AssetsManifestPlugin {
public createAssets(compilation: Compilation) {
const entries = {};
const pages = {};
const assets = {};
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) {
const entryName = entrypoint.name;
@ -50,6 +58,7 @@ export default class AssetsManifestPlugin {
publicPath: compilation.outputOptions?.publicPath,
entries,
pages,
assets,
};
const manifestFileName = resolve(this.outputDir, this.fileName);

View File

@ -38,11 +38,12 @@
"postcss": "^8.4.7",
"postcss-modules": "^4.3.1",
"lodash.merge": "^4.6.2",
"mrmime": "^1.0.0",
"prettier": "^2.5.1",
"sass": "^1.49.9",
"semver": "^7.3.5",
"webpack": "^5.72.0",
"temp": "^0.9.4",
"webpack": "^5.69.1",
"webpack-dev-server": "^4.7.4"
},
"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 type { Plugin } from '@ice/types';
import createAssetsPlugin from '../../esbuild/assets.js';
import generateHTML from './ssr/generateHTML.js';
import { setupRenderServer } from './ssr/serverRender.js';
@ -8,16 +9,20 @@ const webPlugin: Plugin = ({ registerTask, context, onHook }) => {
const { ssg = true, ssr = true } = userConfig;
const outputDir = path.join(rootDir, 'build');
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 () => '';
onHook(`before.${command as 'start' | 'build'}.run`, async ({ esbuildCompile }) => {
serverCompiler = async () => {
await esbuildCompile({
entryPoints: [path.join(rootDir, '.ice/entry.server')],
outdir: path.join(outputDir, 'server'),
outfile: serverEntry,
// platform: 'node',
format: 'esm',
outExtension: { '.js': '.mjs' },
plugins: [
createAssetsPlugin(assetsManifest, rootDir),
],
});
// timestamp for disable import cache
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);
}),
}),
...(buildOptions.plugins || []),
...transformPlugins
// ignore compilation-plugin while esbuild has it's own transform
.filter(({ name }) => name !== 'compilation-plugin')

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ describe(`build ${example}`, () => {
page = res.page;
browser = res.browser;
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);
}, 120000);