一、簡單介紹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 文件,自動刷新當前頁面