- 最近有一个项目,考虑到要进行 SEO,所以要做成多页面应用。为了保证开发速度和开发效率,所以决定使用 webpack 做一套模块化配置方案。
- 下面主要针对一些重要的点提供思路,并不作详解。完整的代码,我会放在 github()上供大家参考,如果有优化的地方,请在评论区指点出来,。
目录
|-- build webpack 配置| |-- utils.js 处理 webpack 配置的公共方法| |-- webpack.base.conf.js 公共配置 | |-- webpack.dev.conf.js 开发环境配置| |-- webapck.prod.conf.js 生产环境配置| |-- webpack.rules.conf.js 文件处理规则|-- dist 存放变异后文件|-- ||-- src 源文件| |-- assets| |-- pages | | |-- index 首页| | | |-- index.html 首页模板| | | |-- index.js 首页入口文件| htmlarrary.js 页面配置文件复制代码
多页面
多页面,首先最重要的就是处理多个 html 模板和对应的多个入口文件。
html 模板
在项目根目录创建一个 htmlarrary.js
,用来存储页面配置:
// htmlarrary.jsmodule.exports = [ { _html: 'index', title: '首页', chunks: ['index', 'manifest', 'vendors'] // 页面用到的vendor模块 }, { _html: 'login', title: '登录', chunks: ['login'] }]复制代码
然后在 /build/utils.js
创建 getHtmlArray
方法,用来自动生成多个模板的配置:
// /build/utils.jsconst HtmlWebpackPlugin = require('html-webpack-plugin')const htmlArray = require('../htmlarray.js')exports.getHtmlArray = function (moduleExportsPlugins) { // 根据模板配置生成 HtmlWebpackPlugin 需要的配置 const getHtmlConfig = function (name, chunks, title) { return { template: `./src/pages/${name}/index.html`, filename: `./${name}.html`, favicon: './src/assets/images/public/favicon.ico', title, inject: true, hash: true, // 开启hash chunks, // 页面要引入的包 minify: process.env.NODE_ENV === 'development' ? false : { removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 折叠空白区域 也就是压缩代码 removeAttributeQuotes: true, // 去除属性引用 }, }; }; // 循环创建模板配置 htmlArray.forEach((element) => { const { _html, chunks, title } = element moduleExportsPlugins.push(new HtmlWebpackPlugin(getHtmlConfig(_html, chunks, title))) })}复制代码
在 webpack.base.conf.js
中通过 getHtmlArray
添加多页面引擎配置:
const { getHtmlArray } = require('./utils.js')module.exports = { // ... 相关配置}getHtmlArray(module.exports.plugins)复制代码
入口文件
在 /build/utils.js
创建 getEntry
方法,用来自动生成入口文件的配置:
// /build/utils.jsconst glob = require('glob')exports.getEntry = function () { const entry = {} // 读取src目录所有page入口 glob.sync('./src/pages/*/*.js').forEach((name) => { const start = name.indexOf('src/') + 4; const end = name.length - 3; const eArr = []; const n = name.slice(start, end).split('/')[1]; eArr.push(name); eArr.push('@babel/polyfill'); // 引入这个,是为了用async await,一些IE不支持的属性能够受支持,兼容IE浏览器用的 entry[n] = eArr; }) return entry;}复制代码
在 webpack.base.conf.js
中通过 getEntry
添加多入口配置:
// webpack.base.conf.jsconst { getEntry } = require('./utils.js')module.exports = { entry: getEntry(),}复制代码
JS
JS 方面,我们一般有以下需求:
- eslint 错误提醒;
- ts-loader 解析 typescript 语法;
- babel-loader 解析 ES6 语法。
针对以上需求,我们来配置一下子 rules,并且做一下延伸:
// webpack.rules.conf.jsmodule.exports = [ { test: /\.(js|ts)$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', targets: { chrome: '58', ie: '8' }, corejs: 2 }] ] } }, { loader: 'ts-loader' }, { loader: 'eslint-loader', options: { cache: true // 优化打包速度 } } ] }]复制代码
在生产环境,我们需要对 js 文件进行压缩,公共代码抽离,所以还需要在 webpack.prod.conf.js
中这样去优化一下:
// webpack.prod.conf.jscconst merge = require('webpack-merge')const UglifyJsPlugin = require('uglifyjs-webpack-plugin')const baseConfig = require('./webpack.base.conf.js')const prodConfig = { optimization: { minimizer: [ // 会导致 sourcemap 消失 new UglifyJsPlugin({ uglifyOptions: ({ compress: false }) }), new OptimizeCSSAssetsPlugin({}) ], splitChunks: { chunks: 'all', cacheGroups: { vendors: { // 抽离第三方插件 test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包 name: 'vendors', priority: -10 // 抽取优先级 }, utilCommon: { // 抽离自定义 name: 'common', minSize: 0, // 将引用模块分离成新代码文件的最小体积 minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk priority: -20 } } }, // optimization.runtimeChunk 就是告诉 webpack 是否要把这部分单独打包出来,来优化缓存问题 runtimeChunk: { name: 'manifest' } }}module.exports = merge(baseConfig, prodConfig)复制代码
CSS
CSS 方面,我们一般有以下需求:
- postcss-loader 安装 autoprefixer 插件,自动进行兼容性处理;
- sass-loader 解析 sass 语法;
- MiniCssExtractPlugin 进行 css 压缩。
针对以上需求,我们来配置一下子 rules,并且做一下延伸:
// webpack.rules.conf.jsconst MiniCssExtractPlugin = require('mini-css-extract-plugin')module.exports = [ { test: /\.scss$/i, use: [ Object.assign( // 生产环境压缩 css 需要使用 MiniCssExtractPlugin.loader 代替 style-loader { loader: process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'style-loader' }, // 解决编译后 css 图片不能正常显示的问题 process.env.NODE_ENV === 'production' ? { options: { publicPath: '../' } } : {} ), 'css-loader', 'sass-loader', 'postcss-loader' ] }]复制代码
在生产环境,我们需要对 css 文件进行压缩,所以还需要在 webpack.prod.conf.js
中这样去优化一下:
// webpack.prod.conf.jscconst merge = require('webpack-merge')const MiniCssExtractPlugin = require('mini-css-extract-plugin')const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')const baseConfig = require('./webpack.base.conf.js')const prodConfig = { optimization: { minimizer: [ new OptimizeCSSAssetsPlugin({}) ], }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css', chunkFileName: '[id].[contenthash:8].css' }), ]}module.exports = merge(baseConfig, prodConfig)复制代码
images
images 方面,我们一般有以下需求:
- css 和 js 中的图片可以被解析;
- html 中 img 标签的图片可以被解析。
针对以上需求,我们来配置一下子 rules,并且做一下延伸:
// webpack.rules.conf.jsconst MiniCssExtractPlugin = require('mini-css-extract-plugin')module.exports = [ { test: /\.html$/, use: [ // 如果 img 标签的 src 为空的话,就报错 xxxHTMLLINKxxx0. { loader: 'html-loader', } ] }, { test: /\.(png|jpg|gif|ico)$/, use: [ { loader: 'url-loader', options: { name: '[name].[hash:8].[ext]', limit: 30000, outputPath: './images' } } ] }]复制代码
其他
devserver 和 热更新
// webpack.dev.conf.jsconst devConfig = { devServer: { open: true, host: '0.0.0.0', port: 2000, useLocalIp: true, hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() ]}复制代码
这样智能启动 css 热更新,如果需要 js 热更新,需要添加一段代码,请自行查找 。
报错
- 如果 img 标签的 src 为空的话,就报错 xxxHTMLLINKxxx0.
- 如果报错:
TS2688: Cannot find type definition file for 'unist'.
说明需要安装依赖@types/unist
,其他类似报错一样,这是typescript@2.0
更换 types 支持方式导致的报错。 - 编译后 css 图片路径错误,根据是否是生产环境来动态添加
publicPath
,。