过去的一段时间里头我在搞一个官网的项目,搭建一个普通的html+scss+js的原生web前端项目,顺便实践了一波webpack常用的打包流程。
首先是项目源码结构

webpack配置
清空dist文件夹
每次打包之前需要清空打包的输出目录,这边采用的方法是使用nodeJs的fs模块递归遍历打包的输出目录,并把里面的内容完全清空。
//清空打包输出目录
function delDir(path){
let files = [];
if(fs.existsSync(path)){ //判断是否存在该路径
files = fs.readdirSync(path); //同步读取该路径下的文件列表
files.forEach((file, index) => { // 遍历文件列表
let curPath = path + "/" + file; // 拼成绝对路径
if(fs.statSync(curPath).isDirectory()){ //判断该文件是否文件夹
delDir(curPath); //递归删除文件夹
} else {
fs.unlinkSync(curPath); //删除文件
}
});
fs.rmdirSync(path); // 清空目录下面的内容之后删除这个目录
}
}
delDir(buildPath);
手动写这一段的原因是项目有打包文件输出到项目根目录外的需求,如果没有这个需求可以使用clean-webpack-plugin插件清空打包目录。
安装插件:npm install clean-webpack-plugin --save-dev
使用:
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
plugin: [
new CleanWebpackPlugin(buildPath)
]
}
多入口js、html配置
html的打包处理使用的是HtmlWebpackPlugin插件。由于官网是多页面应用,因此需要把各个页面的js配置到entry中、html则需要拼成htmlPlugin实例列表配置到plugin中。由于项目的页面目录比较固定(每个页面都有一个html,scss和js。并且放在同名的目录下面),因此可以使用nodeJs来拼装这些配置内容。
let pagesEntry = {}; //页面js入口配置
let pagesHtmlPlugin = []; //页面模板html的HtmlWebpackPlugin插件实例列表
const pagesRoot = path.resolve(__dirname, 'src/pages'); //多页面目录
// 读取page文件目录
const pages = fs.readdirSync(pagesRoot)
pages.forEach(name => {
// 页面js入口配置
const enterPath = path.join(pagesRoot, name)
pagesEntry[name] = path.join(enterPath, name+'.js')
// 输出页面模板html的HtmlWebpackPlugin实例
pagesHtmlPlugin.push(new HtmlWebpackPlugin({
filename: `html/${name}.html`, //输出路径
template: `${enterPath}/${name}.html`, //html源文件路径
inject: true,
minify: process.env.NODE_ENV === "development" ? false : {
removeComments: true, //移除HTML中的注释
collapseWhitespace: true, //折叠空白区域 也就是压缩代码
removeAttributeQuotes: true, //去除属性引用
},
minify: true,
hash: true,
chunks: ['main', name] //每个页面都需要导入main这个chunk
}))
})
module.exports = {
entry: Object.assign({
main: './src/main.js' //通用入口
}, pagesEntry),
plugins: [].concat(pagesHtmlPlugin),
}
于是到这里就完成了全部页面的html模板打包以及js入口的配置,其中HtmlWebpackPlugin
处理js
在上一步中我已经引入了各页面的通用、特有js入口文件,在这之后我们要需要对js文件进行es5转es6等转义处理。
安装相关依赖: npm install babel-core babel @babel/preset-env --save-dev
使用babel处理js文件:
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/, //不处理依赖库里面的代码
loader: 'babel-loader'
}]
}
}
新建.babelrc文件进行babel配置:
{
"presets": [
[
"@babel/preset-env", {
"targets": { // 配置需要支持的浏览器版本
"chrome": "67",
"ie": "10"
}
}
]
]
}
需要注意的是,上面的配置仅仅是对es6的语法进行转义,并没有引入es6新增的一些api入Promise,Set,Symbol,Array.from,async等等。如果需要兼容这些api,还需要配置babel-polyfill或者babel-transform-runtime等工具。官网项目中需要的js逻辑不太多,就都没有使用这些api,所以这里就暂时不展开了。
处理scss样式文件
项目中使用scss预处理器来编写样式。首先是安装相关的依赖库:
npm install node-sass sass-loader style-loader css-loader mini-css-extract-plugin autoprefixer --save-dev
其中node-sass需要比较长时间下载,因为需要从github下载代码,好像也没有什么特别好的办法,只能等了。
webpack中处理css:
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, // 把样式抽出为文件形式(而不是base64或者style标签内联的形式)
'css-loader', // 处理css中的模块化语法例如@import、url()
'postcss-loader', // css后处理
'sass-loader' // 把sass语法编译成css
]
}
]
},
plugins: [
new MiniCssExtractPlugin({filename: "css/[name].css"})
]
}
添加postcss.config.js进行postcss-loader的配置:
module.exports = {
plugins: [
require('autoprefixer') //自动给一些样式添加浏览器兼容前缀如'-ms-'和'-webkit-'
]
}
处理静态资源文件
最后则是处理项目中用到的静态资源如图片、字体文件等,这些资源不需要对其中的内容进行处理,但是需要从源代码目录移动到打包输出目录中去以及在引用处加上hash等,用于刷新浏览器缓存。
安装依赖: npm install file-loader --save-dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options: {
name: 'img/[name].[ext]?[hash:7]',
publicPath: '..'
}
}
}]
}
}
如果没有加hash的需求也可使用copy-webpack-plugin插件直接把文件移动到打包目录中去:
安装依赖: npm install copy-webpack-plugin --save-dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, './src/static/img'), //把staic/img目录里头的图片移动到[buildPath]/img目录下
to: 'img'
}
])
]
}
以上,便是一个普通官网项目需要用到的打包配置实践的全部内容。
最后是完整webpack配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const fs = require('fs');
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const buildPath = process.env.NODE_ENV === "development" ? path.resolve(__dirname, 'dist') : path.resolve(__dirname, '../official/src/main/resources/dist'); //打包输入位置
const pagesRoot = path.resolve(__dirname, 'src/pages'); //多页面目录
let pagesEntry = {};
let pagesHtmlPlugin = [];
//清空打包输出目录
function delDir(path){
let files = [];
if(fs.existsSync(path)){ //判断是否存在该路径
files = fs.readdirSync(path); //同步读取该路径下的文件列表
files.forEach((file, index) => { // 遍历文件列表
let curPath = path + "/" + file; // 拼成绝对路径
if(fs.statSync(curPath).isDirectory()){ //判断该文件是否文件夹
delDir(curPath); //递归删除文件夹
} else {
fs.unlinkSync(curPath); //删除文件
}
});
fs.rmdirSync(path); // 清空目录下面的内容之后删除这个目录
}
}
delDir(buildPath);
// 读取page文件目录
const pages = fs.readdirSync(pagesRoot)
pages.forEach(name => {
// 页面js入口配置
const enterPath = path.join(pagesRoot, name)
pagesEntry[name] = path.join(enterPath, name+'.js')
// 输出页面模板html的HtmlWebpackPlugin实例
pagesHtmlPlugin.push(new HtmlWebpackPlugin({
filename: `html/${name}.html`, //输出路径
template: `${enterPath}/${name}.html`, //html源文件路径
inject: true,
minify: process.env.NODE_ENV === "development" ? false : {
removeComments: true, //移除HTML中的注释
collapseWhitespace: true, //折叠空白区域 也就是压缩代码
removeAttributeQuotes: true, //去除属性引用
},
minify: true,
hash: true,
chunks: ['main', name] //每个页面都需要导入main这个chunk
}))
})
module.exports = {
mode: 'development',
devtool: process.env.NODE_ENV === "development" ? "cheap-module-eval-source-map" : "",
entry: Object.assign({
main: './src/main.js'
}, pagesEntry),
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options: {
name: 'img/[name].[ext]?[hash:7]',
publicPath: '..'
}
}
}, {
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
]
}]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new MiniCssExtractPlugin({filename: "css/[name].css?[hash:7]"}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: 'jquery'
}),
].concat(pagesHtmlPlugin),
optimization: {
usedExports: true
},
output: {
filename: 'js/[name].js',
path: buildPath,
chunkFilename: '[name].js?[hash:7]'
}
}