webpack4.x实践多页面官网项目打包

524 阅读4分钟

过去的一段时间里头我在搞一个官网的项目,搭建一个普通的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]'
	}
}