前言
在日常开发中,我们通常会选择脚手架vue-cli
、create-react-app
等来帮助开发。可是了解并掌握webpack
来完成项目工程化开发却是一门基本功
本文基于:
webpack: 4.41.2
node: 10.15.1
yarn: 1.16.0
webpack基本配置
1.1 初始化React项目
- 在新建目录下
yarn init
- 安装React相关包
yarn add react react-dom axios
- 安装babel
在开发中,我们经常使用ES6+的语法,但是并非所有浏览器都有将ES6+语法转换成javascript的能力,所以这就是我们为何使用babel的原因,babel能将ES6+的语法转换为兼容的JS语法-
安装
babel
的核心包和命令行工具
yarn add @babel/core
yarn add @babel/cli
-
安装
babel
的预置配置和语法插件
yarn add @babel/preset-env
支持ES6语法
yarn add @babel/preset-react
支持React语法
yarn add @babel/plugin-proposal-class-properties
支持类属性 -
编辑
.babelrc
使安装的插件和配置生效{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "entry", "corejs": 3 } ], "@babel/preset-react" ], "plugins": [ "@babel/plugin-proposal-class-properties" ] }
-
安装
babel-polyfill
- babel默认只转换语法,但不转换对象。可开发时由于原浏览器没有例如Promise、Symbol这些对象的语法,我们需要使用时,就得对babel的配置做些调整
- 我们可以使用
babel-polyfill
来解决这一问题。其原理是自己实现了ES6+的所有对象和实例方法,但缺点是污染了全局空间和内置对象,可能会出现同名对象冲突
yarn add @babel/polyfill
-
1.2 使用webpack
- 安装
yarn add webpack webpack-cli
- 新建
src/index.js
作为全局入口import "@babel/polyfill" // 放在开头 import React from 'react' import ReactDOM from 'react-dom' import axios from 'axios'
- 创建
webpack.config.js
const path = require('path'); module.exports = { entry: './src/index.js', // 入口文件 output: { // 出口文件(编译后) filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, // babel-loader将根据.babelrc去处理文件 module: { rules: [ {test: /\.js$/, use: 'babel-loader'} ] } };
yarn add babel-loader
1.3 集成webapck-dev-server
yarn add webapck-dev-server
快速搭建本地运行环境yarn add html-webpack-plugin
在内存中生成html- 修改
package.json
"scripts": { "dev": "webpack-dev-server --open --port 8081 --contentBase src --hot", "build": "webpack --mode production", "test": "echo \"Error: no test specified\" && exit 1" },
- 修改
webpack.config.js
const path = require('path'); const htmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: path.join(__dirname, './src/index.js'), output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new htmlWebpackPlugin({ template: path.join(__dirname, './index.html'), filename: 'index.html' }) ], module: { rules: [ {test: /\.js$/, use: 'babel-loader'} ] }, mode: 'development' };
- 在集成webpack-dev-server、http-webpack-plugin后,访问localhost:8081(在内存中的html页面)对于在html中引入css、图片、less、字体等文件会报错,需要另外处理
yarn add less less-loader css-loader style-loader file-loader
- 修改
webpack.config.js
rules: [ {test: /\.js$/, use: 'babel-loader'}, {test: /\.css$/, use: ['style-loader', 'css-loader']}, {test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']}, {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'} ]
- 修改index.html
<script src="bundle.js"></script>
1.4 配置React路由
yarn add react-router react-router-dom
- 在配置了
react-router
后,无法通过输入地址或刷新子路由访问页面
原因: 我们通过react-router配置的只是前端路由,但webpack不会识别前端路由,在服务端匹配不到路径后,会将其自动转到404界面
解决:在webpack.config.js
中配置:// webpack在匹配不到对应路径后,会默认返回首页 // 这时将由前端路由控制 devServer: { historyApiFallback: true, }
1.5 区分开发环境和生产环境
- 在
webpack.config.js
中对开发模式和生产模式做区分:const path = require('path'); module.exports = function(env, argv){ // 1. 通过 argv 判断是开发还是生产环境 const isEnvDevelopment = argv.mode === 'development' || !argv.mode; const isEnvProduction = argv.mode === 'production'; return{ // 2. mode和devtool根据环境做不同配置 mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', devtool: isEnvProduction ? 'source-map' : isEnvDevelopment && 'cheap-module-source-map', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }] } } }
1.6 开发配置
Source Map
:将目标代码映射到原始代码
经过打包器后,浏览器执行的是目标代码。而在开发中,出错时希望报的错能直接定位到原始代码中,而不是显示目标代码,这时我们需要配置source map
const path = require('path'); const htmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = function(env, argv){ const isEnvDevelopment = argv.mode === 'development' || !argv.mode; const isEnvProduction = argv.mode === 'production'; return{ mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', devtool: isEnvProduction ? 'source-map' : isEnvDevelopment && 'cheap-module-source-map', // 修改 entry: path.join(__dirname, './src/index.js'), output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, plugins: [ new htmlWebpackPlugin({ template: path.join(__dirname, './public/index.html'), filename: 'index.html' }) ], module: { rules: [ {test: /\.js$/, use: 'babel-loader'}, {test: /\.css$/, use: ['style-loader', 'css-loader']}, {test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']}, {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'} ] }, devServer: { historyApiFallback: true, contentBase: './dist', hot: true } } }
- Eslint 与 Prettier
- Eslint的作用是检查代码是否符合规范,并进行提示
- Prettier一般配合ESlints使用,ESlints能检测出代码是否规范,但不能完全统一代码风格,而Prettier统一了代码风格,但不能检测代码规范
yarn add eslint babel-eslint
yarn add eslint-config-airbnb
yarn add eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
- 创建
.eslintrc.js
module.exports = { // parser用于指定解析器,eslint本身无法解析es6语法,需要转换成babel-eslint "parser": "babel-eslint", // extends表示以airbnb为基础规范 "extends": ["airbnb"], // env告诉eslint,如果碰到browser和es6的对象,不用报undefined "env": { browser: true, es6: true, }, // rules用于指定扩展规范规则,会覆盖airbnb规则中的配置 "rules": { 'react/jsx-filename-extension': [1, {extensions: ['.js']}], 'react/prop-types': 0, 'react/prefer-stateless-function': 0, 'react/no-array-index-key': 0, 'no-console': 0, 'jsx-ally/anchor-is-valid': 0, 'react/destructuring-assignment': 0, 'react/jsx-one-expression-per-line': 0 } }
- 手动执行
eslint
yarn eslint src/
检查src/下的js文件
npx eslint --fix src/
自动修复不规范,只列出那些无法自动修复的
- 配置路径别名
在开发中,我们经常会配置路径别名,快速定位文件,提高效率和文件引入的准确度,通常是用@
来表示src
目录resolve: { alias: { '@': path.resolve('src') } }
- 模块化CSS
- 单纯使用
style-loader、css-loader
,虽然可以在JS中引入CSS样式文件,但随之出现的问题是:- 全局污染:
CSS
文件中的选择器是全局的,不同文件的同名选择器,根据build
后生成文件中的先后顺序,后面的样式会覆盖前面的样式 - 选择器复杂,命名规则混乱
- 全局污染:
- 而使用CSS Module模块化处理后,针对每一个JS文件,其引入的CSS文件都只对该文件生效,这有点类似vue中
scoped
属性起到的作用 - 修改
webpack.config.js
{test: /\.css$/, use: ['style-loader', 'css-loader?modules']},
- 使用示例:
- CSS Module的原理:对每个类名按照一定规则进行转换(加上hash值),保证其唯一性,但是只对className和id能进行转换
- 单纯使用
- CSS兼容性处理
使CSS能针对不同浏览器添加不同的前缀,解决浏览器兼容问题yarn add postcss-loader autoprefixer
- 配置
postcss.config.js
module.exports = { plugins: { 'autoprefixer': {} } };
- 修改
webpack.config.js
// postcss-loader要放在less-loader前,先从less转为css,再用postcss处理 module: { rules: [ {test: /\.js$/, exclude: /node_modules/, enforce: "pre", use: 'babel-loader'}, {test: /\.css$/, include: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader', 'postcss-loader']}, {test: /\.css$/, exclude: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader?modules', 'postcss-loader']}, {test: /\.less$/, include: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']}, {test: /\.less$/, exclude: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader?modules', 'postcss-loader', 'less-loader']}, {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'}, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], loader: 'url-loader', options: { limit: 10000 } } ], },
1.7 生产环境配置
- 复制
webpack.config.js
,重命名为webpack.config.prod.js
- 编辑
package.json
"scripts": { "dev": "webpack-dev-server --port 8081 --mode development", "build": "webpack --config webpack.prod.config.js --mode production", "watch": "webpack --watch", "test": "echo \"Error: no test specified\" && exit 1" },
- 修改
webpack.prod.js
删除devServer
配置
结尾
到此,我们可以使用以下命令:
yarn run dev
开发环境下启动项目,在端口8081访问
yarn run build
打包编译项目文件,生成dist目录
至于一些webpack的优化配置,如:代码分离、文件体积压缩、配置缓存等以及开发环境中使用HMR模块热更新则不再过多介绍,或者后续将会继续更新