详解多页应用 Webpack4 配置优化与踩坑记录(2)

稍微了解过 webpack 运行机制的同学会知道,项目工程中加载的 module,webpack 会为其分配一个 moduleId,映射对应的模块。这样产生的问题是一旦工程中模块有增删或者顺序变化,moduleId 就会发生变化,进而可能影响所有 chunk 的 content hash 值。只是因为 moduleId 变化就导致缓存失效,这肯定不是我们想要的结果。

在 webpack4 以前,通过 HashedModuleIdsPlugin 插件,我们可以将模块的路径映射成 hash 值,来替代 moduleId,因为模块路径是基本不变的,故而 hash 值也基本不变。

但在 webpack4 中,只需要optimization的配置项中设置 moduleIds 为 hashed 即可。

namedChunks

除了 moduleId,我们知道分离出的 chunk 也有其 chunkId。同样的,chunkId 也有因其 chunkId 发生变化而导致缓存失效的问题。由于manifest与打出的 chunk 包中有chunkId相关数据,所以一旦如“增删页面”这样的操作导致 chunkId 发生变化,可能会影响很多的 chunk 缓存失效。

在 webpack4 以前,通过增加NamedChunksPlugin,使用 chunkName 来替换 chunkId,实现固化 chunkId,保持缓存的能力。在 webpack4 中,只需在optimization的配置项中设置 namedChunks 为 true 即可。

css 相关

在 webpack4 以前,使用 extract-text-webpack-plugin 插件将 css 从 js 包中分离出来单独打包。在 webpack 中则需要换成 MiniCssExtractPlugin。并且在生产环境或者需要 HMR(模块热替换)时,要用 MiniCssExtractPlugin.loader 替换 style-loader。

注意,这里有个坑。由于开发环境我们会配置热更新,css 的热更新目前MiniCssExtractPlugin.loader自身还待支持,故而还需要增加 css-hot-loader。 切记,css-hot-loader一定不能在生产环境下使用。否则每次构建过程所有 js chunk 包的 contentHash 值都会不一致,进而导致所有 js 缓存失效。 因为生产环境增加这个配置不会有任何报错,页面也能正常构建,故而容易忽视。

简化多页应用的入口文件

使用react/vue等框架的同学知道,我们一般需要一个入口index.js,如这样:

import React from 'react' import ReactDOM from 'react-dom' import App from './app' ReactDOM.render(<App />, document.getElementById('root'))

如果你还需要使用dva,或者给所有 react 页面增加一个 layout 功能的话,可能就会变成这样:

import React from 'react' import dva from 'dva' import Model from './model' import Layout from '~@/layout' import App from './app' const app = dva() app.router(() => ( <Layout> <App /> </Layout> )) app.model(Model) app.start(document.getElementById('root'))

如果每个页面都这样,略略有点儿难受,因为程序员最怕写重复的东西了。但是它又必须要有,没办法抽离成一个单独文件。因为这个是入口文件,而多页工程,每个页面必须要有自己的入口文件,即使他们长得一模一样。于是,我们的资源目录就会是这样:

- src - layout.js - pages - pageA - index.js - app.js - model.js - pageB - index.js - app.js - model.js

因为所有的 index 都一样,我理想中的页面的入口文件仅仅需要app.js就好,像这样:

- src - layout.js - pages - pageA - app.js - model.js - pageB - app.js - model.js

作为一名前端开发工程师,Node 对于我们来说,应该是熟练运用的工具,而不是仅仅拿别人已经封装好的各类工具。

在这个问题中,我们大可以在 webpack 构建前,通过Node的文件系统(File System),对应我们的每个页面,通过同一个入口文件模板,创建一些临时入口文件:

- src - .entires - pageA.js - pageB.js - layout.js - pages

然后将这些临时文件,作为 webpack 的 entry 配置。代码如下:

const path = require('path') const fs = require('fs') const glob = require('glob') const rimraf = require('rimraf') const entriesDir = path.resolve(process.cwd(), './src/.entries') const srcDir = path.resolve(process.cwd(), './src') // 返回webpack entry配置 module.exports = function() { if (fs.existsSync(entriesDir)) { rimraf.sync(entriesDir) } fs.mkdirSync(entriesDir) return buildEntries(srcDir) } function buildEntries(srcDir) { return getPages(srcDir).reduce((acc, current) => { acc[current.pageName] = buildEntry(current) return acc }, {}) } // 获取页面数据,只考虑一级目录 function getPages(srcDir) { const pagesDir = `${srcDir}/pages` const pages = glob.sync(`${pagesDir}/**/app.js`) return pages.map(pagePath => { return { pageName: path.relative(pagesDir, p).replace('/app.js', ''), // 取出page文件夹名 pagePath: pagePath } }) } // 构建临时入口文件 function buildEntry({ pageName, pagePath }) { const fileContent = buildFileContent(pagePath) const entryPath = `${entriesDir}/${pageName}.js` fs.writeFileSync(entryPath, fileContent) return entryPath } // 替换模板中的 App 模块地址,返回临时入口文件内容 function buildFileContent(pagePath) { return ` import React from 'react' import dva from 'dva' import Model from './model' import Layout from '~@/layout' import App from 'PAGE_APP_PATH' const app = dva() app.router(() => ( <Layout> <App /> </Layout> )) app.model(Model) app.start(document.getElementById('root')) `.replace(PAGE_APP_PATH, pagePath) }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/390d37942ddd0a7a0c906f5a54d20c7f.html