In the past period of time, I was engaged in an official website project, which built a common HTML + SCSS + JS native Web front-end project, and incidentally practiced a wave of packaging process commonly used by Webpack.

The first is the project’s source code structure

Webpack configuration

Clear the dist folder

Before each packaging, the packaged output directory needs to be emptied. The method adopted here is to use nodeJs FS module to recursively traverse the packaged output directory and completely empty the contents inside.

// Clear the package output directory
function delDir(path){
	let files = [];
	if(fs.existsSync(path)){    // Check whether the path exists
			files = fs.readdirSync(path);   // Read the list of files in this path synchronously
			files.forEach((file, index) = > {    // Iterate over the list of files
					let curPath = path + "/" + file;    // Create an absolute path
					if(fs.statSync(curPath).isDirectory()){ // Determine whether the file is a folder
							delDir(curPath); // Delete folders recursively
					} else {
							fs.unlinkSync(curPath); // Delete files}}); fs.rmdirSync(path);// Delete the directory after clearing the contents under it
	}
}
delDir(buildPath);
Copy the code

The reason for writing this section manually is that the project has a requirement to package files for output outside the project root directory. If this requirement is not available, the clean-webpack-plugin can be used to clear the package directory. NPM install clean-webpack-plugin –save-dev

const CleanWebpackPlugin  = require('clean-webpack-plugin');
module.exports = {
    plugin: [
        new CleanWebpackPlugin(buildPath)
    ]
}
Copy the code

Multi-entry JS and HTML configuration

HTML packaging is processed using the HtmlWebpackPlugin. As the official website is a multi-page application, js of each page needs to be configured into entry, and HTML needs to be configured into a list of htmlPlugin instances. The project has a fixed page directory (each page has an HTML, SCSS, and JS. And placed under a directory of the same name), so you can use nodeJs to assemble these configurations.

let pagesEntry = {};    // Page js entry configuration
let pagesHtmlPlugin = [];   // List of HtmlWebpackPlugin instances for page template HTML
const pagesRoot = path.resolve(__dirname, 'src/pages');	// Multi-page directory
// Read the page file directory
const pages = fs.readdirSync(pagesRoot)
pages.forEach(name= > {
	// Page js entry configuration
	const enterPath = path.join(pagesRoot, name)
	pagesEntry[name] = path.join(enterPath, name+'.js')
	// HtmlWebpackPlugin instance of the output page template HTML
	pagesHtmlPlugin.push(new HtmlWebpackPlugin({
		filename: `html/${name}.html`.// Output path
		template: `${enterPath}/${name}.html`.// HTML source file path
		inject: true.minify: process.env.NODE_ENV === "development" ? false : {
			removeComments: true.// Remove comments from HTML
			collapseWhitespace: true.// Folding white space is the same as compressing code
			removeAttributeQuotes: true.// Remove attribute references
		},
		minify: true.hash: true.chunks: ['main', name]  // Each page needs to import the chunk main}})))module.exports = {
        entry: Object.assign({
	    main: './src/main.js'   // Universal entrance
	}, pagesEntry),
	plugins: [].concat(pagesHtmlPlugin),
}
Copy the code

So here to complete the whole page of HTML template packaging and JS entry configuration, HtmlWebpackPlugin

Deal with js

In the previous step, I introduced the generic, unique JS entry file for each page. After that, we need to escape the JS file from ES5 to ES6. NPM install babel-core babel@babel /preset-env –save-dev

    module.exports = {
        module: {
            rules: [{
                test: /\.js$/.exclude: /node_modules/.// Do not process code that depends on the library
                loader: 'babel-loader'}}}]Copy the code

Create a. Babelrc file to configure Babel:

{" presets ": [[" @ Babel/preset - env," {" targets ": {/ / configuration needs to support the browser version" chrome ", "67", "ie" : "10"}}]]}Copy the code

It is important to note that the above configuration is just to escape the es6 grammar, and no es6 add some API are introduced into the Promise, the Set, the Symbol, Array. The from, async and so on. To be compatible with these apis, you need to configure tools such as babel-Polyfill or babel-Transform-Runtime. The official website project does not require too much JS logic, so none of these apis are used, so I will not expand here for the time being.

Process SCSS style files

SCSS preprocessor is used in the project to write styles. The first step is to install the associated dependency libraries:

npm install node-sass sass-loader style-loader css-loader mini-css-extract-plugin autoprefixer --save-dev
Copy the code

Node-sass takes a long time to download because you need to download the code from Github, so there is no good solution but to wait. Handling CSS in Webpack:

    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    module.exports = {
        module: {
            rules: [
                {
                    test: / \. SCSS $/, use: [MiniCssExtractPlugin loader, / / the style out for file (rather than a base64 or the form of an inline style tag)'css-loader'// Handle modular syntax in CSS such as @import, URL ()'postcss-loader'// CSS post-processing'sass-loader'}]}, plugins: [new MiniCssExtractPlugin({filename:"css/[name].css"}})]Copy the code

Add postcss.config.js to configure postCSs-loader:

    module.exports = {
      plugins: [
      	require('autoprefixer') // Automatically add browser-compatible prefixes such as '-ms-' and '-webkit-' to some styles]}Copy the code

Process static resource files

Finally, it deals with the static resources used in the project, such as images, font files, etc., which do not need to be processed, but need to be moved from the source directory to the packaged output directory, hash references, etc., to refresh the browser cache. 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: '.. '}}}]}}Copy the code

NPM install copy-webpack-plugin –save-dev if there is no need to hash the file directly into the package directory

    const CopyWebpackPlugin = require('copy-webpack-plugin')
    module.exports = {
        plugins: [
            new CopyWebpackPlugin([
                {
                    from: path.resolve(__dirname, './src/static/img'// Move the images from staic/img to [buildPath]/img:'img')]}}]Copy the code

That’s all the packaging configuration practices a common official website project needs.

Finally, the full WebPack configuration

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'); // Pack the input position const pagesRoot = path.resolve(__dirname,'src/pages'); // Multi-page directorylet pagesEntry = {};
letpagesHtmlPlugin = []; // Clear the package output directoryfunction delDir(path){
	let files = [];
	if(fs.existssync (path)){// Check whether the path exists. Files = fs.readdirsync (path); ForEach ((file, index) => {// Traverses the file listlet curPath = path + "/"+ file; // Create an absolute pathif(fs.statsync (curPath).isdirectory ()){// Check whether this file is folder delDir(curPath); // Recursively delete folder}else{ fs.unlinkSync(curPath); // Delete file}}); fs.rmdirSync(path); }} delDir(buildPath); // Const pages = fs.readdirsync (pagesRoot) pages. ForEach (name => {// path.join(pagesRoot, name) pagesEntry[name] = path.join(enterPath, name+'.js'Push (new HtmlWebpackPlugin({filename: 'HTML /${name}.html ', // output path template: '${enterPath}/${name}.html ', // HTML source file path inject:true,
		minify: process.env.NODE_ENV === "development" ? false : {
			removeComments: true// collapseWhitespace in HTMLtrue// Fold the blank area, which is the zip code removeAttributeQuotes:true// Remove attribute references}, minify:true.hash: true,
		chunks: ['main'}))}) module.exports = {mode: exports () {mode: exports () {mode: exports ();'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]'}}Copy the code