mirror of https://github.com/alibaba/ice.git
				
				
				
			feat: optimize output folder (#115)
* feat: optimize folder * fix: assets transform * fix: optimze code * fix: optimize code
This commit is contained in:
		
							parent
							
								
									54163342ef
								
							
						
					
					
						commit
						b50d478103
					
				|  | @ -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 | 
|  | @ -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" | ||||
|   } | ||||
| } | ||||
|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -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, | ||||
|     }), | ||||
|  |  | |||
|  | @ -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, | ||||
|     }, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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": { | ||||
|  |  | |||
|  | @ -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; | ||||
|  | @ -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()}`; | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
|  | @ -85,6 +85,9 @@ export interface AssetsManifest { | |||
|   publicPath: string; | ||||
|   entries: string[]; | ||||
|   pages: string[]; | ||||
|   assets?: { | ||||
|     [assetPath: string]: string; | ||||
|   }; | ||||
| } | ||||
| export interface AppContext { | ||||
|   appConfig: AppConfig; | ||||
|  |  | |||
|  | @ -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" | ||||
|   } | ||||
| } | ||||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue