月下三兄贵!Webpack和它的小伙伴vue&&react脚手架基配

1,514 阅读4分钟

简单来说 Webpack 是一个模块打包器。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

webpack让一切变得不再复杂

官网:webpack.docschina.org/

webpack是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle

webpack 本身是基于node.js开发的。

webpack v4.0.0 开始,可以不用引入一个配置文件(零配置文件),然而webpack仍然还是高度可配置的。

1. 安装webpack

目前*@vue/clicreate-react-app基本上采用的是webpack 4.0以上版本,所以本次课程以第四代版本为主;第四代版本需要我们安装webpackwebpack-cli*(可执行命令);

为了防止全局安装webpack的版本冲突,我们真实项目开发的时候基本上以安装在本地项目中为主;

$ npm init -y

$ npm install webpack webpack-cli --save-dev
OR
$ yarn add webpack webpack-cli -D

2.webpack的基础使用

  • 初步体验(零配置)

    /*
     * 默认会打包SRC目录中的JS文件(入口默认index.js)
     * 打包完成的目录默认是DIST/MAIN.JS
     *
     * npx:http://www.ruanyifeng.com/blog/2019/02/npx.html
     * 默认执行node_modules/bin/webpack.cmd文件
     * webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
     */
    $ npx webpack
    
  • 自定义基础配置

    • webpack.config.js OR webpackfile.js
    let path = require('path');
    module.exports = {
    	//=>打包模式  开发环境development  生产环境production
    	mode: 'production',
    	//=>入口
    	entry: './src/index.js',
    	//=>输出
    	output: {
    		//=>输出文件的文件名
    		filename: 'bundle.js',
    		//=>输出目录的"绝对路径"
    		path: path.resolve(__dirname, 'dist')
    	}
    }
    
  • 自定义配置文件名

    • $ npx webpack --config webpack.config.development.js
    • 可在package.json中配置可执行的脚本命令(区分开发环境)
    "scripts": {
        "serve": "webpack --config webpack.config.development.js",
        "build": "webpack --config webpack.config.production.js"
    },
    

3.webpack-dev-server

webpack.js.org/configurati…

  • 安装:$ yarn add webpack-dev-server -D

  • 基础配置

    /* webpack.config.js */
    //=>配置DEV-SERVER
    devServer: {
        //=>端口
    	port: 3000,
        //=>显示编译进度
        progress: true,
        //=>指定访问资源目录
        contentBase: './dist',
        //=>自动打开浏览器
        open: true
    }
    
    /* package.json */
    "scripts": {
        "serve": "webpack-dev-server",
        "build": "webpack"
    }
    
    • $ npm run serve
    • $ npx webpack-dev-server
  • 代码更改后,会自动重新编译,然后自动刷新页面

4.html-webpack-plugin

www.webpackjs.com/plugins/htm…

  • 安装:$ yarn add html-webpack-plugin -D

  • 在webpack.config.js中使用

    let HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        ...,
        //=>在webpack中使用插件
    	plugins: [
    		new HtmlWebpackPlugin({
    			//=>指定自己的模板
    			template: './src/index.html',
    			//=>输出的文件名
    			filename: 'index.html',
    			//=>给引入的文件设置HASH戳(清除缓存的),也可以在output中设置 filename: 'bundle.[hash].js' 来生成不同的文件
    			hash: true,
    			//=>控制是否以及以何种方式最小化输出 
        		//=>https://github.com/kangax/html-minifier
    			minify: {
    				collapseWhitespace: true,
    				removeComments: true,
    				removeAttributeQuotes: true,
    				removeEmptyAttributes: true
    			}
    		})
    	]
    }
    

5.webpack中的加载器loader:处理样式的

  • 安装:$ yarn add css-loader style-loader less less-loader autoprefixer postcss-loader ... -D

  • 使用

    module.exports = {
        //=>配置模块加载器LOADER
    	module: {
    		//=>模块规则:使用加载器(默认从右向左执行,从下向上)
    		rules: [{
    			test: /\.(css|less)$/, //=>基于正则表达式匹配哪些模块需要处理
    			use: [
    				"style-loader", //=>把CSS插入到HEAD中
    				"css-loader", //=>编译解析@import/URL()这种语法
                    "postcss-loader", //=>设置前缀
                    {
    					loader: "less-loader",
                        options: {
                            //=>加载器额外的配置
                        }
    				}
    			]
    		}]
    	}
    }
    

    postcss.config.js

    module.exports = {
    	plugins: [
    		require('autoprefixer')
    	]
    };
    

    package.json

    "browserslist": [
        "> 1%",
        "last 2 versions"
    ]
    

6.mini-css-extract-plugin 抽离CSS内容

www.npmjs.com/package/min…

  • 安装 $ yarn add mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin -D
let MiniCssExtractPlugin = require('mini-css-extract-plugin'),
	OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'),
	UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
    //=>设置优化项
	optimization: {
		//=>设置压缩方式
		minimizer: [
			//=>压缩CSS(但是必须指定JS的压缩方式)
			new OptimizeCssAssetsWebpackPlugin(),
			//=>压缩JS
			new UglifyjsWebpackPlugin({
				cache: true, //=>是否使用缓存
				parallel: true, //=>是否是并发编译
				sourceMap: true, //=>启动源码映射(方便调试)
			})
		]
	},
	plugins: [
		//=>使用插件
		new MiniCssExtractPlugin({
			//=>设置编译后的文件名字
			filename: 'main.css'
		})
	],
	module: {
		rules: [{
			test: /\.(css|less)$/,
			use: [
				// "style-loader",
				//=>使用插件中的LOADER代替STYLE方式
				MiniCssExtractPlugin.loader,
				"css-loader",
                "postcss-loader",
				"less-loader"
			]
		}]
	}
}

上述JS压缩对于require/import等还存在问题,需要对于ES6中的一些语法进行处理!

7.基于babel实现ES6的转换和ESLint语法检测

babeljs.io/

eslint.org/

  • 安装 $ yarn add babel-loader @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-transform-runtime -D
  • 安装 $ yarn add @babel/runtime @babel/polyfill
  • 安装 $ yarn add eslint eslint-loader -D
module.exports = {
	...,
	module: {
		rules: [...,{
			test: /\.js$/,
			use: [{
				loader: 'babel-loader',
				options: {
					//=>转换的语法预设(ES6->ES5)
					presets: [
						"@babel/preset-env"
					],
					//=>基于插件处理ES6/ES7中CLASS的特殊语法
					plugins: [
						["@babel/plugin-proposal-decorators", {
							"legacy": true
						}],
						["@babel/plugin-proposal-class-properties", {
							"loose": true
						}],
						"@babel/plugin-transform-runtime"
					]
				}
			}], //=>, "eslint-loader"
			//=>设置编译时忽略的文件和指定编译目录
			include: path.resolve(__dirname, 'src'),
			exclude: /node_modules/
		}]
	}
}

参考eslint.org/demo生成 .eslintrc.json

补充知识:在vscode中开启ES7中类的装饰器,项目根目录中设置 jsconfig.json

{
	"compilerOptions": {
		"experimentalDecorators": true
	}
}
@log
class A{
	a=1;
}

8.暴露全局loader

  • $ yarn add expose-loader -D
  • 前置加载器、后置加载器、普通加载器...
//=>内联加载器
import jquery from 'expose-loader?$!jquery';
console.log(window.$);

{
   //=>只要引入JQUERY就在全局注入$
   test: require.resolve('jquery'),
   use: ['expose-loader?$']
}
let webpack = require('webpack');
module.exports = {
    plugins: [
		//=>在每个模块中都注入$
		new webpack.ProvidePlugin({
			'$': 'jquery'
		})
	],
}

//=>页面中
console.log($);

9.webpack中图片的处理和分目录分发

  • 在JS中创建IMG
  • 在CSS中设置背景图
  • 在HTML中写死
  • ...

安装 $ yarn add file-loader url-loader html-withimg-loader -D

module.exports = {
	...,
	module: {
		//=>模块规则:使用加载器(默认从右向左执行)
		rules: [..., {
			test: /\.(png|jpg|gif)$/i,
			use: [{
				//=>把指定大小内的图片BASE64
				loader: 'url-loader',
				options: {
					limit: 200 * 1024,
    				outputPath:'/images'
				}
			}],
			include: path.resolve(__dirname, 'src'),
			exclude: /node_modules/
		}, {
			test: /\.html$/,
			use: ['html-withimg-loader']
		}]
	}
}

10. 最后实现文件分目录发布

module.exports = {
	output: {
		//=>配置引用前缀(所有资源前加这个地址)
		publicPath: './'
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: 'css/main.css'
		})
	],
	module: {
		//=>模块规则:使用加载器(默认从右向左执行)
		rules: [...,{
			test: /\.(png|jpg|gif)$/i,
			use: [{
				options: {
					outputPath: 'images'
				}
			}]
		}]
	}
}

合体!

/* 
	较完整的webpack基置
    在这个文件中设置我们自定义的打包规则
        1. 所有的规则都写在module.exports={}中
*/
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let MiniCssExtractPlugin = require('mini-css-extract-plugin'), //把css单独分离出来
    OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'), //压缩css
    UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin'); //压缩js
let webpack = require('webpack');
//导入进来的插件都是一个类  new HtmlWebpackPlugin({});

module.exports = {
    //配置优化规则
    optimization: {
        //设置压缩方式
        minimizer: [
            //压缩css  (产生问题:JS压缩不在执行自己默认的压缩方式了,也走的是这个插件,从而导致无法压缩,所以必须设置JS的压缩方式)
            new OptimizeCssAssetsWebpackPlugin(),
            //压缩js
            new UglifyjsWebpackPlugin({
                cache: true, //是否使用缓存
                parallel: true, //是否是兵法编译
                sourceMap: true, //启动源码映射(方便调试)
            })
        ]
    },
    //配置环境 开发环境development  生产环境production(默认)
    mode: 'production',
    //入口
    entry: ['@babel/polyfill', './src/index-my.js'],
    //出口
    output: {
        //输出文件的文件名
        //bundle.min.[hash].js 让每一次生成的文件名都带着hash值
        filename: 'bundle.min.js',
        // filename: 'bundle.min.[hash].js',
        //输出的目录必须是绝对路径,__dirname当前目录
        path: path.resolve(__dirname, 'dist'),
        publicPath: './' //给编译后引入资源地址前面设置的前缀
    },
    //关于webpack-dev-server的一些配置  执行命令:webpack-dev-server --config xxx.js (特点:服务启动后,默认是不关闭的,当我们修改src中的源文件,他会自动进行编译,然后自动刷新浏览器,类似于vscode中的Live Server插件,实时刷新)
    devServer: {
        //创建服务指定的端口号
        port: 3000,
        //显示打包编译进度
        progress: true,
        //指定当前服务处理资源的目录
        contentBase: './dist',
        //编译完成后,自动打开浏览器
        open: true
    },
    //使用插件 (数组)
    plugins: [
        new HtmlWebpackPlugin({
            //不指定模版会按照默认模版创建一个html页面,当然真实项目中一般都是把自己写好的html进行编译
            template: './src/index.html',
            //输出的文件名
            filename: 'index.html',
            //让我们引入js后面加上hash戳(清除缓存),但是真实项目中我们一般都是每一次编译生成不同的js文件引入
            hash: true,
            //控制压缩
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            }
        }),
        new MiniCssExtractPlugin({
            //指定输出的文件名
            filename: 'main.min.css'
        }),
        //在每个模块中都注入$
        new webpack.ProvidePlugin({
            '$': 'jquery'
        }),
    ],
    //使用加载器loader处理规则
    module: {
        rules: [{
            //基于正则匹配处理哪些文件
            test: /\.(css|less)$/,
            //使用哪一个加载器,控制使用的loader(有顺序的:从右到左执行)
            use: [
                // "style-loader", //把编译好的css插入到页面的head中(内嵌式)
                MiniCssExtractPlugin.loader, //使用插件中的loader代替style方式
                "css-loader", //编译解析@import/URL()这种语法
                // "postcss-loader",//设置前缀的加载器
                {
                    loader: "postcss-loader",
                    options: {
                        ident: 'postcss',
                        plugins: [
                            require('autoprefixer')
                        ]
                    }
                },
                {
                    loader: "less-loader", //编译less
                    options: {
                        //加载额外的配置
                    }
                }
            ]
        }, {
            test: /\.js$/,
            //处理编译JS的loader
            use: [{
                loader: 'babel-loader',
                options: {
                    //转换的语法预设(ES6->ES5)
                    presets: [
                        "@babel/preset-env"
                    ],
                    //=>基于插件处理ES6/ES7中CLASS的特殊语法
                    plugins: [
                        ["@babel/plugin-proposal-decorators", {
                            "legacy": true //处理装饰器
                        }],
                        ["@babel/plugin-proposal-class-properties", {
                            "loose": true //处理属性
                        }],
                        "@babel/plugin-transform-runtime"
                    ]
                }
            }],
            //设置编译时忽略的文件和指定编译目录
            include: path.resolve(__dirname, 'src'), //编译的
            exclude: /node_modules/ //忽略的·
        }, {
            //图片处理
            test: /\.(png|jpg|gif|jpeg|ico|webp|bpm)$/i,
            use: [{
                loader: 'url-loader',
                options: {
                    //只要图片小于200KB,在处理的时候直接base64
                    limit: 2 * 1024,
                    //控制打包后图片所在的目录
                    outputPath: 'images'
                }
            }]
        }, {
            //处理HTML文件中导入的img文件
            test: /\.(html|htm|xml)$/i,
            use: ['html-withimg-loader']
        }]
    }
}

修改 Vue-cli3 webpack内部配置

  • vue-cli3的配置,全部放在了vue.config.js这一个文件中,讲道理,真的清爽。
  • 创建vue.config.js文件命令来审查或修改全局的 CLI 配置。
module.exports = {
	//=>process.env.NODE_ENV:环境变量中存储的是开发环境还是生产环境
	publicPath: process.env.NODE_ENV === 'production' ? 'http://www.baidu.com/' : '/',
	//=>outputDir
	//=>自定义目录名称,把生成的JS/CSS/图片等静态资源放置到这个目录中
	assetsDir: 'assets',
	//=>关闭生产环境下的资源映射(生产环境下不在创建xxx.js.map文件)
	productionSourceMap: false,
	//=>设置一些webpack配置项,用这些配置项和默认的配置项合并
	configureWebpack: {
		plugins: []
	},
	//=>直接去修改内置的webpack配置项
	chainWebpack: config => {
		//=>config:原始配置信息对象
		config.module
			.rule('images')
			.use('url-loader')
			.loader('url-loader')
			.tap(options => {
				options.limit = 200 * 1024;
				return options;
			});
	},
	//=>修改webpack-dev-server配置(尤其是跨域代理)
	devServer: {
		proxy: {
			//请求地址  /user/add
			//代理地址  http://api.baidu.cn/user/add
			"/": {
				changeOrigin: true,
				target: "http://api.baidu.cn/"
			}
		}
	},
	//=>多余1核cpu时:启动并行压缩
	parallel: require('os').cpus().length > 1
}   

修改 create-react-app webpack内部配置

  • react 不同,它是导出所有Webpack配置让开发者自行更改
  • yarn eject 命令 => 把所有隐藏在node_modules中的webpack配置项都暴露出来(方便自己根据项目需求,二次更改webpack配置)
  • 打开导出的config文件夹就可以增删改查了 比如修改less的处理配置
  • $ yarn add less less-loader //安装

打开config/webpack.config.js

const cssRegex = /\.(css|less)$/;
const cssModuleRegex = /\.module\.(css|less)$/;

{
   test: cssRegex,
   exclude: cssModuleRegex,
   use: getStyleLoaders({
     importLoaders: 1,
     sourceMap: isEnvProduction && shouldUseSourceMap,
   }, "less-loader"),
   // Don't consider CSS imports dead code even if the
   // containing package claims to have no side effects.
   // Remove this when webpack adds a warning or an error for this.
   // See https://github.com/webpack/webpack/issues/6571
   sideEffects: true,
 },
 // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
 // using the extension .module.css
 {
   test: cssModuleRegex,
   use: getStyleLoaders({
     importLoaders: 1,
     sourceMap: isEnvProduction && shouldUseSourceMap,
     modules: true,
     getLocalIdent: getCSSModuleLocalIdent,
   }, "less-loader"),
 },

我真是 [HIGH] 到不行啦!