一、简单介绍webpack
在现在的工作中,基本上都会使用到 webpack 这个打包工具。目前 webpack 的版本已经到了4.x,在官网说 webpack4.0.0 以上的,可以不去配置而直接使用 webpack 打包。
先安装 webpack 相关依赖:
npm i webpack webpack-cli --save-dev
然后目录是这样子的:
然后 package.json 这样设置:
然后 npm run dev 或者 npm run build 即可打包,实现了最简单的打包使用。
webpack 有四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)
入口:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始;
输出:告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
loader:让 webpack 拥有处理非 JavaScript 文件的能力
plugin:可以用于执行范围更广的任务,比如压缩,定义环境变量等
二、本次使用的webpack的目的
之前使用 webpack 的是在 vue 开发中才有用到,不过大多数时候都是用 vue 脚手架去搭建整个项目,故 webpack 基本上是已经配置好了的,并不需要怎么去配置其内容。
此次的 webpack 的配置构建是为了来打包多入口(多页面)的,学习如何配置 webpack
三、思路以及一些我认为比较需要注意的
1. 多入口
对 webpack 配置的思路其实不难,本次要做的是多入口,那么很简单,官网有说到直接就在entry写多个键值对
const config = { entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' } }
这样子就能实现多个入口了。但是多页面呢?思路就跟entry那里一样,在 plugins 这里 new 多个 html-webpakc-plugin ,就能够打包出来多个 html 页面了。那么问题来,如果每次多一个页面,不就得去 webpack 的配置里面多写一下入口和 new 一下 html-webpack-plugin 了,这太麻烦了。所以,就需要写一个额外的脚本来自动生成 entry 和 new htmlwebpackplugin()。
要能够自动生成相对应的入口的 html 文件,就必须要先订好 src 下的目录规则。我这边约定好的规则:比如页面名字 test.html ,在 js 文件夹下则创建 test 为文件名的文件夹,然后创建唯一入口文件夹 index.js ,然后如果有多个其他的 js 文件,则在 index.js 内 import 进来。
有了上面提到的规则,那么接下来就是需要遍历 src/pages 下的 html 文件,提取文件名,然后通过文件名,遍历得到js入口的对象,同时生成多个 html-webpack-plugin ,最后导出。遍历文件夹内部的文件,可以使用 glob 这个插件(跳到 glob 的 npm 页面),这个插件是用来匹配符合条件的文件,会把符合的以路径的形式返回出来给你。
具体代码可查看下面源代码展示。
2. 代码分块
webpack4 之前的代码分包是使用 CommonsChunkPlugin,在 webpack4 之后,是集成在内部了,在optimization.splitChunks 中配置代码分包。在配置这个之前,我还不清楚怎样分包,每次打包出来后,每个js文件都比原来的测试文件多出来10k左右的大小。后面查找官网,发现已经集成在了 optimization.splitChunks。在 splitChunks 中,它已经有了自己的默认配置:
module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, minRemainingSize: 0, maxSize: 0, minChunks: 1, maxAsyncRequests: 6, maxInitialRequests: 4, automaticNameDelimiter: '~', automaticNameMaxLength: 30, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } }
因为它的一些参数有默认的配置,故我这边 webpack 的 splitchunk 只配置一个 vendor,专门打包来自 node_modules 的依赖。
splitChunks: { cacheGroups: { // 打包node_modules中的文件 vendor: { name: "vendor", test: /[\\/]node_modules[\\/]/, chunks: "all", priority: 10 } } }
配置之后进行打包,业务代码的js体积跟原来的相比并没有多大变化,并多出来了个 vendor 的公共js文件
四、 webpack配置的源代码
本项目源代码:https://github.com/VintageLin/multiple_page_webpack
webpack 的代码结构如图:
文件解读:
config.CONSTANT.js: webpack 配置的一些常量
config.DEV.js: 开发环境调用的 webpack 配置
config.entries.js: webpack 入口及 HTML 模板插件生成
config.PROD.js: 生产环境调用的 webpack 打包配置
webpack.config.js: webpack 的一些通用配置
照例贴上代码,代码里面已经写好了一些注释,并配合 github 项目的 README.md 文档,方便理解
config.CONSTANT.js:
module.exports = {
INITIAL_PORT: 9000, // 初始端口
STOP_PORT: 9999, // 结束端口号
HOST: 'localhost' // 使用一个 host,如果外部需要访问则变成0.0.0.0
}
config.DEV.js:
const merge = require('webpack-merge') // webpack设置合并插件
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin') // 使webpack在控制台输出更友好
const portfinder = require('portfinder') // 端口寻找
const chalk = require('chalk') // 用来美化控制台输出
const log = console.log
const webpackUniversalConfig = require('./webpack.config.js')
const CONSTANT = require('./config.CONSTANT')
const config = {
mode: 'development',
devServer: {
compress: true, // gzip压缩
port: CONSTANT.INITIAL_PORT, // 端口
hot: true, // hot reload
overlay: true, // 出现编译器错误或警告时,在浏览器中显示全屏覆盖层
stats: 'minimal',
quiet: true,
host: CONSTANT.HOST
},
plugins: []
}
module.exports = new Promise((resolve, reject) => {
// 端口查找
portfinder.getPort({
port: CONSTANT.INITIAL_PORT, // 起始
stopPort: CONSTANT.STOP_PORT // 结束
}, function (err, res) {
if (err) {
log(chalk.red('在当前设定的区间无可使用的端口!'))
process.exit()
} else {
log(chalk.green('当前DEV可运行的端口:', res))
config.devServer.port = res
config.plugins.push(new FriendlyErrorsWebpackPlugin({
compilationSuccessInfo: {
messages: [`当前项目运行在 http://${CONSTANT.HOST}:${res}`]
}
}))
// 合并设置
const config_DEV = merge(config, webpackUniversalConfig)
resolve(config_DEV)
}
})
})
config.entries.js:
const glob = require('glob') // 用来获取匹配相对应规则的文件
const htmlWebpackPlugin = require('html-webpack-plugin') // 输出html文件
// 遍历页面寻找src/js/ 下的index.js
let htmlArray = glob.sync('./src/pages/*.html')
let jsEntries = {}
let pagesName = htmlArray.map(item => {
let newItem = item.slice(item.lastIndexOf('/') + 1)
newItem = newItem.replace('.html', '')
jsEntries[newItem] = [`./src/js/${newItem}/index.js`]
return newItem
})
// 生成html模板
let htmlWebpackPluginArray = []
pagesName.forEach(item => {
htmlWebpackPluginArray.push(new htmlWebpackPlugin({
filename: `${item}.html`, // 保存的文件名
template: `./src/pages/${item}.html`, // 生成后的文件保存位置
chunks: ['vendor', item] // 这里是你页面需要引用到的js
}))
})
let entries = {
jsEntries,
htmlWebpackPluginArray
}
module.exports = entries
config.PROD.js:
const { CleanWebpackPlugin } = require("clean-webpack-plugin") // 清空打包文件夹内容
const merge = require('webpack-merge')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css插件
const UglifyWebpackPlugin = require('uglifyjs-webpack-plugin') // 压缩js插件
const webpackUniversalConfig = require("./webpack.config.js")
const config = {
mode: 'production',
output: {
// 公共目录定位到当前文件夹下
publicPath: './'
},
performance: {
hints: false
},
plugins: [
// 每次打包前清空打包目标文件夹
new CleanWebpackPlugin()
],
optimization: {
minimizer: [
// js压缩
new UglifyWebpackPlugin({
parallel: 4
}),
// css压缩
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css/, // 需要优化压缩的文件名
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true
})
],
// 代码拆分
splitChunks: {
cacheGroups: {
// 打包node_modules中的文件
vendor: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
chunks: "all",
priority: 10
}
}
}
}
}
const config_PROD = merge(config, webpackUniversalConfig)
module.exports = config_PROD
webpack.config.js:
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取css到文件,并且在head里面插入link标签引用
const entries = require('./config.entries')
module.exports = {
entry: entries.jsEntries,
output: {
path: path.resolve(__dirname, '../build'),
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].js'
},
// 控制台打印输出配置
stats: {
entrypoints: false,
chunks: false,
children: false,
chunkModules: true,
modules: false
},
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
require("autoprefixer")
]
}
}
],
},
{
test: /\.(jpg|jpeg|png|gif)$/,
loader: 'url-loader',
options: {
name: 'images/[name].[hash].[ext]',
limit: 10000
}
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader?cacheDirectory=true', 'eslint-loader']
},
{
test: /\.html$/,
loader: 'html-loader'
}
]
},
resolve: {
// 模块别名,方便import
alias: {
'@': path.resolve(__dirname, './src')
},
// 自动解析扩展名
extensions: [".js", ".json"]
},
plugins: [
// 页面模板
...entries.htmlWebpackPluginArray,
// 抽离css到单独文件
new MiniCssExtractPlugin({
filename: 'css/[name].css?[hash]',
chunkFilename: '[id].[hash].css',
ignoreOrder: false
})
]
}
以上为主要的 webpack 配置,具体还是移步 github
附:一些遇到的小问题
1. 打包后引用路径不正确,这里的设置公共目录的位置,不能在 webpack.config.js 里面写成通用的,否则就会引起开发环境和生产环境的一些目录差异,因为生产环境的可能是多级目录下访问这个页面,所以只能在 config.PROD.js 里面定义,开发环境则为默认配置。感觉这个也不算是个问题,是我自己没理解好就写到通用配置文件里面了。
2. 在 webpack4 之前是使用 extract-text-webpack-plugin,在webpack4 之后是使用 mini-css-extract-plugin,一开始没仔细看文档,也没去 npm 官网搜下,拿来直接就用了,然后就发生错误,忘记截图目前官网已经推荐使用 mini-css-extract-plugin 了,npm 搜索后也有提示。这个问题也是属于自己没有好好查看文档造成的
3. 去除打包后的 console 和 debugger 等
4. 上面有说到代码分块,代码分块后,还得把公共代码引用到页面上去,这里就得在 html-webpack-plugin 里面的 chunks 参数设置,比如说这样子
最后暂未解决的问题:
实现修改 html 文件,自动刷新当前页面