diff --git a/docs/plugin/develop.md b/docs/plugin/develop.md index f68d60e77068..b765d802cfbf 100644 --- a/docs/plugin/develop.md +++ b/docs/plugin/develop.md @@ -402,6 +402,10 @@ api.addHTMLScript({ Add a script to the HTML head. +### modifyHTMLChunks + +Modify chunks in HTML. + ### modifyHTMLWithAST Modify the HTML, based on cheerio. diff --git a/docs/zh/plugin/develop.md b/docs/zh/plugin/develop.md index 92e816513600..f414ceee764e 100644 --- a/docs/zh/plugin/develop.md +++ b/docs/zh/plugin/develop.md @@ -403,6 +403,10 @@ api.addHTMLScript({ 在 HTML 头部添加脚本。 +### modifyHTMLChunks + +修改 chunks 。 + ### modifyHTMLWithAST 修改 HTML,基于 cheerio 。 diff --git a/packages/umi-build-dev/src/PluginAPI.js b/packages/umi-build-dev/src/PluginAPI.js index da472697c9fd..bed6f22b520b 100644 --- a/packages/umi-build-dev/src/PluginAPI.js +++ b/packages/umi-build-dev/src/PluginAPI.js @@ -93,6 +93,7 @@ export default class PluginAPI { 'addHTMLScript', 'addHTMLStyle', 'addHTMLHeadScript', + 'modifyHTMLChunks', 'onGenerateFiles', 'onHTMLRebuild', 'modifyDefaultConfig', diff --git a/packages/umi-build-dev/src/html/HTMLGenerator.js b/packages/umi-build-dev/src/html/HTMLGenerator.js index 51c459b4e376..cce831ede259 100644 --- a/packages/umi-build-dev/src/html/HTMLGenerator.js +++ b/packages/umi-build-dev/src/html/HTMLGenerator.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import { join, relative } from 'path'; +import { join, relative, extname } from 'path'; import { existsSync, readFileSync } from 'fs'; import isPlainObject from 'is-plain-object'; import ejs from 'ejs'; @@ -188,18 +188,16 @@ export default class HTMLGenerator { } getHashedFileName(filename) { - const isProduction = this.env === 'production'; - if (isProduction) { + // css is optional + if (extname(filename) === '.js') { assert( this.chunksMap[filename], `file ${filename} don't exists in chunksMap ${JSON.stringify( this.chunksMap, )}`, ); - return this.chunksMap[filename]; - } else { - return filename; } + return this.chunksMap[filename]; } getContent(route) { @@ -246,6 +244,9 @@ export default class HTMLGenerator { let scripts = []; let styles = []; let headScripts = []; + let chunks = ['umi']; + + if (this.modifyChunks) chunks = this.modifyChunks(chunks); let routerBaseStr = JSON.stringify(this.config.base || '/'); const publicPath = this.publicPath || '/'; @@ -273,8 +274,14 @@ export default class HTMLGenerator { ...(setPublicPath ? [`window.publicPath = ${publicPathStr};`] : []), ].join('\n'), }); - scripts.push({ - src: `<%= pathToPublicPath %>${this.getHashedFileName('umi.js')}`, + + chunks.forEach(chunk => { + const hashedFileName = this.getHashedFileName(`${chunk}.js`); + if (hashedFileName) { + scripts.push({ + src: `<%= pathToPublicPath %>${hashedFileName}`, + }); + } }); if (this.modifyMetas) metas = this.modifyMetas(metas); @@ -286,9 +293,14 @@ export default class HTMLGenerator { if (this.env === 'development' || this.chunksMap['umi.css']) { // umi.css should be the last one stylesheet - links.push({ - rel: 'stylesheet', - href: `<%= pathToPublicPath %>${this.getHashedFileName('umi.css')}`, + chunks.forEach(chunk => { + const hashedFileName = this.getHashedFileName(`${chunk}.css`); + if (hashedFileName) { + links.push({ + rel: 'stylesheet', + href: `<%= pathToPublicPath %>${hashedFileName}`, + }); + } }); } @@ -322,7 +334,9 @@ ${scripts.length ? this.getScriptsContent(scripts) : ''} exportStatic && exportStatic.dynamicRoot ? relPathToPublicPath : publicPath; - html = html.replace(/<%= pathToPublicPath %>/g, pathToPublicPath); + html = html + .replace(/<%= pathToPublicPath %>/g, pathToPublicPath) + .replace(/<%= PUBLIC_PATH %>/g, pathToPublicPath); if (this.modifyHTML) { html = this.modifyHTML(html, { route }); diff --git a/packages/umi-build-dev/src/plugins/commands/dev/createRouteMiddleware.js b/packages/umi-build-dev/src/plugins/commands/dev/createRouteMiddleware.js index 8c10925b8a8d..c25d29039b44 100644 --- a/packages/umi-build-dev/src/plugins/commands/dev/createRouteMiddleware.js +++ b/packages/umi-build-dev/src/plugins/commands/dev/createRouteMiddleware.js @@ -1,4 +1,5 @@ import getHtmlGenerator from '../getHtmlGenerator'; +import chunksToMap from '../build/chunksToMap'; export default function createRouteMiddleware(service) { return (req, res) => { @@ -8,7 +9,10 @@ export default function createRouteMiddleware(service) { res.setHeader('Content-Type', 'text/json'); res.send(JSON.stringify(service.routes)); } else { - const htmlGenerator = getHtmlGenerator(service); + const chunksMap = chunksToMap(service.__chunks); + const htmlGenerator = getHtmlGenerator(service, { + chunksMap, + }); const content = htmlGenerator.getMatchedContent(path); res.setHeader('Content-Type', 'text/html'); res.send(content); diff --git a/packages/umi-build-dev/src/plugins/commands/dev/index.js b/packages/umi-build-dev/src/plugins/commands/dev/index.js index e4211e51db05..85abcd94fb52 100644 --- a/packages/umi-build-dev/src/plugins/commands/dev/index.js +++ b/packages/umi-build-dev/src/plugins/commands/dev/index.js @@ -105,6 +105,7 @@ export default function(api) { startWatch(); }, onCompileDone({ isFirstCompile, stats }) { + service.__chunks = stats.compilation.chunks; service.applyPlugins('onDevCompileDone', { args: { isFirstCompile, diff --git a/packages/umi-build-dev/src/plugins/commands/getHtmlGenerator.js b/packages/umi-build-dev/src/plugins/commands/getHtmlGenerator.js index 99a4e2a844c6..ca7a6f4d2551 100644 --- a/packages/umi-build-dev/src/plugins/commands/getHtmlGenerator.js +++ b/packages/umi-build-dev/src/plugins/commands/getHtmlGenerator.js @@ -22,6 +22,11 @@ export default (service, opts = {}) => { modifyPublicPathStr(str) { return str; }, + modifyChunks(memo) { + return service.applyPlugins('modifyHTMLChunks', { + initialValue: memo, + }); + }, modifyMetas(memo) { return service.applyPlugins('addHTMLMeta', { initialValue: memo, diff --git a/packages/umi-plugin-react/src/index.js b/packages/umi-plugin-react/src/index.js index bf75aed6a8c7..d6d37378c283 100644 --- a/packages/umi-plugin-react/src/index.js +++ b/packages/umi-plugin-react/src/index.js @@ -47,6 +47,13 @@ export default function(api, option) { hardSource: () => require('./plugins/hardSource').default, pwa: () => require('./plugins/pwa').default, + // html tags + chunks: () => require('./plugins/chunks').default, + scripts: () => require('./plugins/scripts').default, + headScripts: () => require('./plugins/headScripts').default, + links: () => require('./plugins/links').default, + metas: () => require('./plugins/metas').default, + // misc dva: () => require('./plugins/dva').default, locale: () => require('./plugins/locale').default, diff --git a/packages/umi-plugin-react/src/plugins/chunks.js b/packages/umi-plugin-react/src/plugins/chunks.js new file mode 100644 index 000000000000..acb3dee8cdba --- /dev/null +++ b/packages/umi-plugin-react/src/plugins/chunks.js @@ -0,0 +1,11 @@ +export default function(api, option) { + api.onOptionChange(newOption => { + option = newOption; + api.rebuildHTML(); + api.refreshBrowser(); + }); + + api.modifyHTMLChunks(() => { + return option; + }); +} diff --git a/packages/umi-plugin-react/src/plugins/headScripts.js b/packages/umi-plugin-react/src/plugins/headScripts.js new file mode 100644 index 000000000000..d715b67b3028 --- /dev/null +++ b/packages/umi-plugin-react/src/plugins/headScripts.js @@ -0,0 +1,11 @@ +export default function(api, option) { + api.onOptionChange(newOption => { + option = newOption; + api.rebuildHTML(); + api.refreshBrowser(); + }); + + api.addHTMLHeadScript(() => { + return option; + }); +} diff --git a/packages/umi-plugin-react/src/plugins/links.js b/packages/umi-plugin-react/src/plugins/links.js new file mode 100644 index 000000000000..72a302562f02 --- /dev/null +++ b/packages/umi-plugin-react/src/plugins/links.js @@ -0,0 +1,11 @@ +export default function(api, option) { + api.onOptionChange(newOption => { + option = newOption; + api.rebuildHTML(); + api.refreshBrowser(); + }); + + api.addHTMLLink(() => { + return option; + }); +} diff --git a/packages/umi-plugin-react/src/plugins/metas.js b/packages/umi-plugin-react/src/plugins/metas.js new file mode 100644 index 000000000000..866a7a916ac8 --- /dev/null +++ b/packages/umi-plugin-react/src/plugins/metas.js @@ -0,0 +1,11 @@ +export default function(api, option) { + api.onOptionChange(newOption => { + option = newOption; + api.rebuildHTML(); + api.refreshBrowser(); + }); + + api.addHTMLMeta(() => { + return option; + }); +} diff --git a/packages/umi-plugin-react/src/plugins/mobile/fastClick.js b/packages/umi-plugin-react/src/plugins/mobile/fastClick.js deleted file mode 100644 index 157c146d86b5..000000000000 --- a/packages/umi-plugin-react/src/plugins/mobile/fastClick.js +++ /dev/null @@ -1 +0,0 @@ -export default from '../fastClick'; diff --git a/packages/umi-plugin-react/src/plugins/mobile/hd.js b/packages/umi-plugin-react/src/plugins/mobile/hd.js deleted file mode 100644 index d4b2ce49bf7e..000000000000 --- a/packages/umi-plugin-react/src/plugins/mobile/hd.js +++ /dev/null @@ -1 +0,0 @@ -export default from '../hd'; diff --git a/packages/umi-plugin-react/src/plugins/scripts.js b/packages/umi-plugin-react/src/plugins/scripts.js new file mode 100644 index 000000000000..abfdf7da2442 --- /dev/null +++ b/packages/umi-plugin-react/src/plugins/scripts.js @@ -0,0 +1,11 @@ +export default function(api, option) { + api.onOptionChange(newOption => { + option = newOption; + api.rebuildHTML(); + api.refreshBrowser(); + }); + + api.addHTMLScript(() => { + return option; + }); +} diff --git a/packages/umi-plugin-react/test/chunks/.umirc.js b/packages/umi-plugin-react/test/chunks/.umirc.js new file mode 100644 index 000000000000..ec10e97777f0 --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/.umirc.js @@ -0,0 +1,30 @@ +export default { + plugins: [ + [ + '../../lib', + { + dynamicImport: { + webpackChunkName: true, + }, + chunks: ['vendors', 'umi'], + }, + ], + ], + chainWebpack(config) { + config.optimization.splitChunks({ + cacheGroups: { + vendors: { + name: 'vendors', + chunks: 'all', + test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)/, + }, + commons: { + name: 'commons', + chunks: 'async', + minChunks: 2, + minSize: 0, + }, + }, + }); + }, +}; diff --git a/packages/umi-plugin-react/test/chunks/a.js b/packages/umi-plugin-react/test/chunks/a.js new file mode 100644 index 000000000000..ba5d0e813943 --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/a.js @@ -0,0 +1 @@ +console.log('module a'); diff --git a/packages/umi-plugin-react/test/chunks/package.json b/packages/umi-plugin-react/test/chunks/package.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/umi-plugin-react/test/chunks/pages/index.css b/packages/umi-plugin-react/test/chunks/pages/index.css new file mode 100644 index 000000000000..a4b84e2ef64c --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/pages/index.css @@ -0,0 +1,4 @@ + +.normal { + background: #95F279; +} diff --git a/packages/umi-plugin-react/test/chunks/pages/index.js b/packages/umi-plugin-react/test/chunks/pages/index.js new file mode 100755 index 000000000000..efa3b266b682 --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/pages/index.js @@ -0,0 +1,10 @@ +import styles from './index.css'; +import '../a'; + +export default function() { + return ( +
+

Page index

+
+ ); +} diff --git a/packages/umi-plugin-react/test/chunks/pages/users.css b/packages/umi-plugin-react/test/chunks/pages/users.css new file mode 100644 index 000000000000..f66d7d6983f1 --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/pages/users.css @@ -0,0 +1,4 @@ + +.normal { + background: #7DF279; +} diff --git a/packages/umi-plugin-react/test/chunks/pages/users.js b/packages/umi-plugin-react/test/chunks/pages/users.js new file mode 100755 index 000000000000..d7e3fc062b2b --- /dev/null +++ b/packages/umi-plugin-react/test/chunks/pages/users.js @@ -0,0 +1,10 @@ +import styles from './users.css'; +import '../a'; + +export default function() { + return ( +
+

Page users

+
+ ); +} diff --git a/packages/umi-plugin-react/test/normal.e2e.js b/packages/umi-plugin-react/test/normal.e2e.js index 798483555d1a..48819bba7d2a 100644 --- a/packages/umi-plugin-react/test/normal.e2e.js +++ b/packages/umi-plugin-react/test/normal.e2e.js @@ -43,6 +43,22 @@ describe('normal', () => { () => document.querySelector('title').innerHTML, ); expect(titleText).toEqual('默认标题'); + + // scripts + const scripts = await page.evaluate(() => window.scripts); + expect(scripts).toEqual(['headScript1', 'script1', 'script2']); + + // links + const link = await page.evaluate(() => + document.querySelector('#link1').getAttribute('foo'), + ); + expect(link).toEqual('bar'); + + // metas + const meta = await page.evaluate(() => + document.querySelector('#meta1').getAttribute('foo'), + ); + expect(meta).toEqual('/bar'); }); it('a page', async () => { diff --git a/packages/umi-plugin-react/test/normal/.umirc.js b/packages/umi-plugin-react/test/normal/.umirc.js index bb1aac8c6238..27521824cee2 100644 --- a/packages/umi-plugin-react/test/normal/.umirc.js +++ b/packages/umi-plugin-react/test/normal/.umirc.js @@ -27,6 +27,14 @@ export default { polyfills: [], antd: true, title: '默认标题', + + headScripts: [{ content: `window.scripts = ['headScript1'];` }], + scripts: [ + { content: `window.scripts.push('script1');` }, + { src: '/script2.js' }, + ], + metas: [{ id: 'meta1', foo: '<%= PUBLIC_PATH %>bar' }], + links: [{ id: 'link1', foo: 'bar' }], }, ], ], diff --git a/packages/umi-plugin-react/test/normal/public/script2.js b/packages/umi-plugin-react/test/normal/public/script2.js new file mode 100644 index 000000000000..15a2921b0235 --- /dev/null +++ b/packages/umi-plugin-react/test/normal/public/script2.js @@ -0,0 +1 @@ +window.scripts.push('script2');