安装 node.js 当前 node.js 版本 :v12.13.1 当前 npm 版本 : 6.12.1
2020/1/6 更新 自己重新按照文档走一遍发现有点遗漏,进行补充
本文从零开始搭建 webpack ,只需要按照步骤一步一步走,最后就可搭建成功 ,请放心食用,无毒
mkdir webpack4-react && cd webpack4-react
npm init -y // 初始化,生成package.json文件
当前 webpack 版本:4.41.5 当前 webpack-cli 版本:3.3.10
npm install --save-dev/-D webpack webpack-cli
或
yarn add --dev/-D webpack webpack-cli
调整 package.json 文件,以便确保我们安装包是私有的(private),并且移除 main 入口。这可以防止意外发布你的代码。
package.json
{
...
"description": "webpack4-react",
"private": true, // 将仓库私有化
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
...
}
npm install --save lodash
或
yarn add lodash
webpack默认的源代码入口就是src/index.js, 默认出口是/dist, 默认配置文件是webpack.config.js, 默认打包的文件名是main.js
webpack4-react
|- package.json
|- /dist
|- index.html
|- /src
|- index.js
package.json
{
"name": "webpack4-react",
"version": "1.0.0",
"description": "webpack4-react",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.15"
},
"devDependencies": {
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
}
}
dist/index.html
<!DOCTYPE html>
<html>
<head>
<title>webpack4-react</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
src/index.js
import _ from 'lodash';
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
npx webpack
将看到以下输出:
Hash: 17a14a12467064d9d4dd
Version: webpack 4.41.5
Time: 1239ms
Built at: 2020-01-04 10:56:16
Asset Size Chunks Chunk Names
main.js 72.1 KiB 0 [emitted] main
Entrypoint main = main.js
[1] ./src/index.js 210 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
+ 1 hidden module
此时在 dist 文件夹下已经生成一个 main.js 文件 在浏览器中打开 dist 下的 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。
简易打包已经完成
webpack4-react
|- package.json
|- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js
webpack.config.js
const path = require('path');
module.exports = {
entry: {
//配置页面入口
index: ['./src/index.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
dist/index.html
...
<body>
<script src="bundle.js"></script>
</body>
...
package.json
{
"name": "webpack4-react",
"version": "1.0.0",
"description": "webpack4-react",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.15"
},
"devDependencies": {
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
}
}
npm run build
终端将输出:
Hash: 9cbb2fac6cc224bfe661
Version: webpack 4.41.5
Time: 1272ms
Built at: 2020-01-04 11:39:26
Asset Size Chunks Chunk Names
main.js 72.1 KiB 0 [emitted] main
Entrypoint main = main.js
[1] ./src/index.js 213 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
+ 1 hidden module
同样的, dist 文件夹下生成 bundle.js 文件
这样就实现了基本的 webpack 构建了
npm install --save react react-dom
npm install --save-dev @babel/cli @babel/core @babel/preset-react @babel/preset-env @babel/plugin-transform-runtime babel-loader @babel/plugin-proposal-class-properties
npm install --save @babel/runtime core-js
src/index.js
import 'core-js/stable'; // 与babel-loader中的配置结合 实现pollyfilly
import 'regenerator-runtime/runtime'; // 与babel-loader中的配置结合 实现pollyfilly
import React from 'react';
import ReactDom from 'react-dom';
const hello = 'Hello React'
ReactDom.render(
<div>
<div>{hello}</div>
</div>,
document.getElementById('app'),
);
dist/index.html
...
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
...
为了使用 babel 解析 jsx
-
- webpack 配置文件中
webpack.config.js
... entry: { //配置页面入口 index: ['./src/index.js'], }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ // 将新的js语法和JSX语法转为es5标准的代码在浏览器中运行 { test: /\.(js|jsx)$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { // 一般配置在.babelrc 文件中 presets: [ [ '@babel/preset-env', // 包含当前所有ECMASCript标准的的最新特性 { useBuiltIns: 'usage', // Babel 会在你使用到 ES2015+ 新特性时,按需引入 babel-polyfill , corejs: 3, // 它是JavaScript标准库的polyfill,仅当与useBuiltIns: usage/entry或一起使用时,此选项才有效 targets: { // 支持最低浏览器环境版本的对象 chrome: '58', ie: '8', }, }, ], '@babel/preset-react', // 支持react的jsx语法 ], }, }, ], }, ], } ...
-
- 添加 babel 配置文件
在根目录下新建 .babelrc 文件
.babelrc
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3, "targets": { "chrome": "58", "ie": "8" } } ], "@babel/preset-react" ], "plugins": [ [ "@babel/plugin-transform-runtime", // 减少冗余代码,使用时还依赖于babel-runtime插件 { "absoluteRuntime": false, "corejs": false, "helpers": true, "regenerator": true, "useESModules": false } ], ["@babel/plugin-proposal-class-properties", { "loose": true } ] ] }
当前文件目录结构:
webpack4-react |- /dist |- index.html |- /src |- index.js |- .babelrc |- package.json |- webpack.config.js
执行 npm run build
终端输出:
```
$ webpack
Hash: f4d46dd4732764195f93
Version: webpack 4.41.5
Time: 446ms
Built at: 2020-01-04 13:28:48
Asset Size Chunks Chunk Names
bundle.js 128 KiB 0 [emitted] main
Entrypoint main = bundle.js
[3] ./src/index.js 211 bytes {0} [built]
+ 7 hidden modules
```
在浏览器中打开 dist 下的 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello React'。
webpack4-react
|- /src
|- index.js
|- index.html
|- .babelrc
|- package.json
|- webpack.config.js
src/index.html
<!DOCTYPE html>
<html>
<head>
<title>webpack4-react</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
-
- 下载依赖
npm install --save-dev webpack-dev-server
-
- webpack 配置文件中配置 webpack-dev-server webpack.config.js
... entry: { //配置页面入口 index: ['./src/index.js'], }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, devServer: { // webpack-dev-serve的配置项 contentBase: '/src', hot: true, }, ...
-
- 下载依赖
npm install --save-dev html-webpack-plugin
-
- webpack 配置文件中配置 webpack-dev-server webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); ... devServer: { contentBase: '/src', hot: true, }, plugins: [ new HtmlWebpackPlugin({ // 自动创建一个html文件,该文件引入了打好的webpack包 template: './src/index.html', filename: './index.html', chunks: ['index'], inject: 'body', // 将打好的包引入在template文件的body元素的下面 }), ], ...
package.json
{
"name": "webpack4-react",
"version": "1.0.0",
"description": "webpack4-react",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --open --config webpack.config.js",
"build": "webpack --config webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.15",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"@babel/cli": "^7.7.7",
"@babel/core": "^7.7.7",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.7.4",
"babel-loader": "^8.0.6",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
}
}
执行
npm run dev
后会自动打开浏览器,此时修改 index.js 文件中内容,浏览器会实时更新
删除 dist 文件夹
执行 npm run build 打包依旧会在 dist 下生成打包文件
为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:
-
- 下载 style-loader css-loader
npm install --save-dev style-loader css-loader
-
- webpack.config.js 中配置 css 的 loader
module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader', ] }, ... }
-
- src/index.js 中引入 css src 下新建 style/reset.css style/reset.css
src/index.js* { padding: 0; margin: 0; } div { font-size: 20px; }
执行import './style/reset.css';
npm run dev
,会看到 reset.css 中的样式已经生效
-
- 下载依赖
npm install --save-dev autoprefixer postcss-loader npm install --save-dev less less-loader node-sass sass sass-loader
这个过程中安装 node-sass 可能会很慢, 耐心等待
-
- 创建 React 组件
创建如下目录文件及内容:
webpack4-react |- src |- components |- Date |- index.jsx |- style.scss |- style |- reset.scss |- index.js |- .babelrc |- package.json |- webpack.config.js
src/components/Date/index.jsx
import React from 'react'; import style from './style.scss'; class DateComponent extends React.Component { constructor(props) { super(props); this.state = { date: new Date(), }; } componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date(), }); } render() { const { date } = this.state; return ( <div> <div className={style.title}>时间</div> <div className={style.title}> {date.toLocaleTimeString()} </div> </div> ); } } export default DateComponent;
src/components/Date/style.scss
.title { height: 50px; font: bold 20px '微软雅黑'; text-align: center; color: #000; }
-
- 引入组件
src/index.js
import React from 'react'; import ReactDom from 'react-dom'; import DateComponents from './components/Date/index.jsx'; import './style/reset.scss'; const hello = 'Hello React'; ReactDom.render( <div> <div>{hello}</div> <DateComponents /> </div>, document.getElementById('app') );
-
- 新增 CSS 相关的 webpack 配置 根目录下新建 tools/utils.js
// css 配置 const styleLoader = { loader: 'style-loader', }; const cssLoader = { loader: 'css-loader', options: { modules: true, // webpack3 为 module sourceMap: true, importLoaders: 2, }, }; const postCssLoader = { loader: 'postcss-loader', }; const sassLoader = { loader: 'sass-loader', options: { sourceMap: true, }, }; const lessLoader = { loader: 'less-loader', }; exports.loadersConfig = { styleLoader, cssLoader, postCssLoader, sassLoader, lessLoader, }; // css 配置
webpack.config.js
... const utils = require('./tools/utils.js'); const { postCssLoader, styleLoader, sassLoader, cssLoader, } = utils.loadersConfig; ... module.exports = { ... module: { rules: [ { test: /\.css$/, exclude: /node_modules/, use: [styleLoader, cssLoader, postCssLoader], }, { test: /\.scss$/, include: [/pages/, /components/, /style/], use: [ styleLoader, cssLoader, postCssLoader, sassLoader, ], }, } ... };
-
- 新增 postcss-loader 配置文件 根目录下新增 postcss.config.js
const AUTOPREFIXER_BROWSERS = [ 'Android 2.3', 'Android >= 4', 'Chrome >= 35', 'Firefox >= 31', 'Explorer >= 8', 'iOS >= 7', 'Opera >= 12', 'Safari >= 7.1', ]; module.exports = { plugins: [require('autoprefixer')({overrideBrowserslist: ['> 0.15% in CN']})], };
执行 npm run dev
,自动打开浏览器,css 相关的配置构建完成
-
- 下载依赖
npm install --save-dev file-loader url-loader
-
- webpack 配置中添加规则
... module: { rules: [ ... { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [ { loader: 'url-loader', options: { name: '[path][name].[ext]', limit: 1024 * 15, fallback: 'file-loader', }, }, ], }, ... ] } ...
-
- React 组件中 引入图片 src/components/Date/index.jsx
... import reactLogo from './../../images/React.svg'; import webpackLogo from './../../images/webpack.svg'; ... render() { const { date } = this.state; return ( <div> <div className={style.title}>时间</div> <div> <img className={style.logo} src={reactLogo} alt="" /> <img className={style.logo} src={webpackLogo} alt="" /> </div> <div className={style.title}> {date.toLocaleTimeString()} </div> </div> ); } ...
字体、数据等参考 webpack 官网 资源管理
在代码引入组件或图片时,我们来配置一些便捷的方式
webpack.config.js
// 引入 node 的 path 模块
const path = require('path');
...
module.exports = {
...
resolve: {
// 设置模块导入规则,import/require时会直接在这些目录找文件
modules: ['node_modules'],
// import导入时省略后缀
extensions: ['.js', '.jsx', '.scss', '.less', '.css', '.json'],
// import导入时别名
alias: {
'@components': path.resolve('./src/components'),
'@images': path.resolve('./src/images'),
'@style': path.resolve('./src/style'),
},
},
...
}
举个 🌰
src/index.js 中
import React from 'react';
import ReactDom from 'react-dom';
import DateComponents from '@components/Date/index.jsx';
import '@style/reset.scss';
const hello = 'Hello React';
ReactDom.render(
<div>
<div>{hello}</div>
<DateComponents />
</div>,
document.getElementById('app')
);
此时 执行 npm run dev
查看
开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
-
- 下载依赖
npm install --save-dev webpack-merge clean-webpack-plugin
-
- 拆分 webpack 配置
根目录下创建 webpack 文件夹
|- webpack |- webpack.common.js |- webpack.dev.js |- webpack.production.js
webpack.common.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const utils = require('./../tools/utils'); const { postCssLoader, styleLoader, sassLoader, cssLoader } = utils.loadersConfig; module.exports = { entry: { //配置页面入口 index: ['./src/index.js'], }, output: { filename: 'bundle.js', path: path.resolve(__dirname, '../dist'), }, devServer: { contentBase: '/src', hot: true, }, resolve: { // 设置模块导入规则,import/require时会直接在这些目录找文件 modules: ['node_modules'], // import导入时省略后缀 extensions: ['.js', '.jsx', '.scss', '.less', '.css', '.json'], // import导入时别名 alias: { '@assets': path.resolve('./src/assets'), '@common': path.resolve('./src/common'), '@components': path.resolve('./src/components'), '@images': path.resolve('./src/images'), '@pages': path.resolve('./src/pages'), '@style': path.resolve('./src/style'), }, }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: './index.html', chunks: ['index'], inject: 'body', }), ], module: { rules: [ { test: /\.css$/, exclude: /node_modules/, use: [styleLoader, cssLoader, postCssLoader], }, { test: /\.scss$/, include: [/pages/, /components/, /style/], use: [styleLoader, cssLoader, postCssLoader, sassLoader], }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [ { loader: 'url-loader', options: { name: '[path][name].[ext]', limit: 1024 * 15, fallback: 'file-loader', }, }, ], }, { test: /\.(js|jsx)$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, targets: { chrome: '58', ie: '8', }, }, ], '@babel/preset-react', ], }, }, ], }, ], }, };
webpack.dev.js
const merge = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'development', devtool: 'inline-source-map', devServer: { contentBase: '/src', hot: true, } });
webpack.production.js
const merge = require('webpack-merge'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'production', devtool: 'source-map', plugins: [ new CleanWebpackPlugin(), ], });
-
- npm 脚本命令更改 package.json
"dev": "webpack-dev-server --open --config webpack/webpack.dev.js", "build": "webpack --config webpack/webpack.production.js"
-
- 设置环境变量 下载依赖
npm install --save-dev cross-env
npm 脚本命令更改
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --config webpack/webpack.dev.js", "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.production.js"
在 npm 脚本执行的时候设置的环境变量通过 process.env.NODE_ENV 来获取,process.env.NODE_ENV 的值 在当前脚本下有两种: development , production , 借此可以根据不同环境设置不同的配置。
-
- 下载依赖
npm install --save-dev html-loader
-
- 配置
在根目录下添加一个 react.ico 的图片待用,用于在 HtmlWebpackPlugin 中配置网页的 ico 图片
webpack.config.common.js
... entry: { //配置页面入口 index: ['./src/index.js'], }, output: { //配置输出选项 path: path.resolve(__dirname, '../dist'), //输出路径为,当前路径下 filename: '[name].[hash:5].js', //输出后的文件名称 }, ... plugins: [ new HtmlWebpackPlugin({ title: 'webpack & react', template: './src/index.html', //本地模板文件的位置,支持加载器(如handlebars、ejs、undersore、html等),如比如 handlebars!src/index.hbs; filename: './index.html', //输出文件的文件名称,默认为index.html,不配置就是该文件名;此外,还可以为输出文件指定目录位置(例如'html/index.html') chunks: ['index'], // chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件 inject: 'body', //1、true或者body:所有JavaScript资源插入到body元素的底部2、head: 所有JavaScript资源插入到head元素中3、false: 所有静态资源css和JavaScript都不会注入到模板文件中 showErrors: true, //是否将错误信息输出到html页面中 hash: false, //是否为所有注入的静态资源添加webpack每次编译产生的唯一hash值 favicon: 'react.ico', //添加特定的 favicon 路径到输出的 HTML 文件中。 minify: { //是否对大小写敏感,默认false caseSensitive: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 默认false collapseBooleanAttributes: true, //是否去除空格,默认false collapseWhitespace: true, //是否压缩html里的css(使用clean-css进行的压缩) 默认值false; minifyCSS: true, //是否压缩html里的js(使用uglify-js进行的压缩) minifyJS: true, //Prevents the escaping of the values of attributes preventAttributesEscaping: true, //是否移除属性的引号 默认false removeAttributeQuotes: true, //是否移除注释 默认false removeComments: true, //从脚本和样式删除的注释 默认false removeCommentsFromCDATA: true, //是否删除空属性,默认false removeEmptyAttributes: true, // 若开启此项,生成的html中没有 body 和 head,html也未闭合 removeOptionalTags: false, //删除多余的属性 removeRedundantAttributes: true, //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false removeScriptTypeAttributes: true, //删除style的类型属性, type="text/css" 同上 removeStyleLinkTypeAttributes: true, //使用短的文档类型,默认false useShortDoctype: true, collapseWhitespace: true, keepClosingSlash: true, minifyURLs: true }, }), ] ... module: { rules: [ // 压缩html文件 { test: /\.html$/, use: 'html-loader', exclude: path.resolve('./src/index.html') }, ] }
-
- 下载依赖
npm install --save-dev mini-css-extract-plugin
-
- 在生产环境中压缩 CSS, 开发环境中不压缩 scss
webpack.config.common.js 其中 关于 .scss$ 的 rules 替换下
... const devMode = process.env.NODE_ENV !== 'production'; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); ... module.exports = { ... plugins: [ ... new MiniCssExtractPlugin(isDev ? { chunkFilename: 'style/[name].[contenthash:8].css', }: { filename: 'style/[name].[contenthash:8].css', }), ... ] ... module: { rules: [ { test: /\.html$/, use: "html-loader" }, { test: /\.css$/, exclude: /node_modules/, use: [styleLoader, cssLoader, postCssLoader] }, { test: /\.scss$/, include: [/pages/, /components/, /style/], use: [ devMode ? styleLoader : MiniCssExtractPlugin.loader, cssLoader, postCssLoader, sassLoader, ], }, ], }
-
- 再次执行
npm run build
会发现在 dist 文件夹里多了些 css,css.map 文件
- 再次执行
-
- 下载依赖
npm install --save-dev uglifyjs-webpack-plugin clean-webpack-plugin
-
- webpack 配置
webpack.production.js
... const merge = require('webpack-merge'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const common = require('./webpack.config.common.js'); module.exports = merge(common, { mode: 'production', // mode为production会自动压缩js文件 devtool: 'source-map', plugins: [ new CleanWebpackPlugin(), ], }); ...
-
- 下载依赖
npm install --save-dev happypack
-
- 配置
webpack.config.common.js
... const HappyPack = require('happypack'); ... plugins: [ ... new HappyPack({ id: 'babel', //用id来标识 happypack处理那里类文件 threads: 1, verbose: false, // 是否允许happyPack输出日志 loaders: [ { loader: 'babel-loader', cacheDirectory: true }, ], }), ... ], ... module: { rules: [ { test: /\.(js|jsx)$/, use: ['happypack/loader?id=babel'], exclude: /node_modules/, //设置node_modules里的js文件不用解析 }, ] } ...
5. polyfill 编译 es6 的新语法 , 等同于bable-loader中配置了"@babel/preset-env"的"useBuiltIns": "usage","corejs": 3,在入口页面
-
- 下载依赖
npm install --save-dev @babel/polyfill @babel/plugin-transform-arrow-functions @babel/preset-es2017
-
- 配置
webpack.common.js
entry: { //配置页面入口 index: ['@babel/polyfill', './src/index.js'], },
-
- 测试语法支持
src/index.js
async function f() { return 'hello world'; } function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } const hw = helloWorldGenerator(); console.log('Generator>>>>>>', hw.next()); const arr = [1, 2, 3, 4, 5, 1, 2, 3, 5]; const setArr = new Set(arr); console.log('setArr?>>>>>>', setArr); const m = new Map(); console.log('Map>>>>>>>>', m); f().then(v => console.log('async>>>>', v)); // IE 不支持 Symbol const helloSymbol = Symbol('www'); console.log('Symbol>>>>>>', helloSymbol); console.log('flat>>>>>>', [1, [2, [3]]].flat(Infinity)); console.log('---------------'); console.log('Promise'); new Promise(resolve => { setTimeout(() => { resolve('hello'); }, 2000); }).then(value => { console.log(value); return new Promise(resolve => { setTimeout(() => { resolve('world'); }, 2000); }); }).then(value => { console.log(`${value} world`); }); console.log('---------------'); const target = {}; const handler = {}; const proxy = new Proxy(target, handler); proxy.a = 'b'; console.log('proxy>>>>>', target.a); // 不支持 // class A { // static name = 'name'; // } // console.log('static class>>>>>', new A());
打包
npm run build
, 把 dist 文件的 index.html 用 IE 打开验证
vs code 格式化插件 使用的是 Prettier - Code formatter
以及 ESLint
|- .vscode
|- setting.json
setting.json
{
"editor.tabSize": 4,
"prettier.singleQuote": true,
"editor.detectIndentation": false,
"editor.renderControlCharacters": true,
"editor.renderWhitespace": "all",
"emmet.includeLanguages": {
"javascript": "javascriptreact"
},
"prettier.trailingComma": "es5",
"emmet.triggerExpansionOnTab": true,
"javascript.implicitProjectConfig.experimentalDecorators": true,
"workbench.colorTheme": "Solarized Light",
"window.zoomLevel": 0,
"prettier.useTabs": true,
"editor.foldingStrategy": "indentation",
"explorer.confirmDelete": false,
"javascript.updateImportsOnFileMove.enabled": "never",
"eslint.validate": [
{
"language": "javascript",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
}
],
"eslint.autoFixOnSave": true
}
-
- 下载依赖
npm i babel-eslint eslint eslint-config-airbnb eslint-friendly-formatter eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks react-dev-utils -D
-
- webpack 的 eslint 配置
根目录下新建 .eslintrc.js 文件 配置参考
module.exports = { root: true, env: { browser: true, // "commonjs": true, es6: true, }, extends: [ // "eslint:recommended", // "plugin:react/recommended" 'airbnb', ], // extends: "eslint:recommended", globals: { $: true, process: true, __dirname: true, }, parser: 'babel-eslint', parserOptions: { //es6的module模式 sourceType: 'module', ecmaFeatures: { experimentalObjectRestSpread: true, jsx: true, }, ecmaVersion: 11, // es2020 }, settings: { 'import/ignore': ['node_modules', 'DynamicForm', '.s?css', '@w*'], }, plugins: ['react', 'react-hooks', 'import', 'jsx-a11y'],
overrides: [{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 11,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
// typescript-eslint specific options
warnOnUnsupportedTypeScriptVersion: true,
},
plugins: ['@typescript-eslint'],
// If adding a typescript-eslint version of an existing ESLint rule,
// make sure to disable the ESLint rule here.
rules: {
// TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906)
'default-case': 'off',
// 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
'no-dupe-class-members': 'off',
// Add TypeScript specific rules (and turn off ESLint equivalents)
// '@typescript-eslint/no-angle-bracket-type-assertion': 'warn',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'warn',
'@typescript-eslint/no-namespace': 'error',
'no-unused-vars': 1,
'@typescript-eslint/no-unused-vars': [
'warn',
{
args: 'none',
ignoreRestSiblings: true,
},
],
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'warn',
},
}],
rules: {
'import/no-unresolved': 0,
'import/extensions': 0,
'import/order': 0,
'import/prefer-default-export': 0,
'react/prop-types': 0,
'react/jsx-filename-extension': 0,
'react/prefer-stateless-function': 0,
'react/jsx-indent': [2, 'tab'],
'react/jsx-indent-props': [2, 'tab'],
'react/jsx-tag-spacing': 0,
'react/jsx-props-no-spreading': 0,
'react/require-default-props': 0,
// // @off 同构应用需要在 didMount 里写 setState
'react/no-did-mount-set-state': 0,
'react/button-has-type': 0,
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn", // Checks effect dependencies
'jsx-a11y/anchor-is-valid': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/mouse-events-have-key-events': 0,
'jsx-a11y/no-noninteractive-element-interactions': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/aria-role': 0,
'jsx-a11y/alt-text': 0,
'jsx-a11y/heading-has-content': 0,
'jsx-a11y/anchor-has-content': 0,
'no-return-assign': 0,
'consistent-return': 0,
'no-console': 0,
'no-plusplus': 0,
'linebreak-style': 0,
'no-unused-expressions': 0,
// 0、1、2分别表示不开启检查、警告、错误
indent: [2, 'tab', { SwitchCase: 1 }], // tab缩进
// 圈复杂度
complexity: [2, 9],
'max-params': [2, 7],
'max-depth': [2, 4],
'no-multiple-empty-lines': 0,
'max-len': [
'error',
{
code: 150,
tabWidth: 4,
ignoreComments: true,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
},
],
'no-tabs': 0,
'object-curly-newline': [
0,
{
ObjectExpression: 'always',
ObjectPattern: { multiline: true },
ImportDeclaration: 'never',
ExportDeclaration: {
multiline: true,
},
},
],
'object-curly-spacing': 0,
'arrow-parens': [2, 'as-needed'],
// 最大回调层数
'max-nested-callbacks': [2, 3],
'no-unused-vars': [
1,
{
argsIgnorePattern: '^React',
varsIgnorePattern: '[Rr]eact|[Ss]tyle',
},
],
'no-extra-boolean-cast': 0,
'array-callback-return': 0,
'no-param-reassign': 0,
'jsx-quotes': [0, 'prefer-double'], //强制在JSX属性(jsx-quotes)中一致使用双引号
'no-underscore-dangle': 0,
'quote-props': 0,
// "no-native-reassign": 2,//不能重写native对象
// // if while function 后面的{必须与if在同一行,java风格。
// "brace-style": [2, "1tbs", { "allowSingleLine": true }],
// // 双峰驼命名格式
// "camelcase": 2,
// // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
// "computed-property-spacing": [2,"never"],
// //允许箭头函数可以省略小括号
// 'arrow-parens': 0,
// 'no-extra-semi': 2, // 不允许多余的分号
// //允许使用async-await函数
// 'generator-star-spacing': 0,
// //在开发环境开启debugger功能,生产环境禁止使用debugger
// 'no-debugger': process.env.NODE_ENV === 'development' ? 0 : 2,
// "quotes": [2, "single"], //单引号
// "no-var": 2, //对var警告
// "semi": ["error", "always"], //不强制使用分号
// "no-irregular-whitespace": 0, //不规则的空白不允许
// "no-alert": 2, //禁止使用alert confirm prompt
// "no-lone-blocks": 0, //禁止不必要的嵌套块
// "no-class-assign": 2, //禁止给类赋值
// "no-cond-assign": 2, //禁止在条件表达式中使用赋值语句
// "no-const-assign": 2, //禁止修改const声明的变量
// "no-delete-var": 2, //不能对var声明的变量使用delete操作符
// "no-dupe-keys": 2, //在创建对象字面量时不允许键重复
// "no-duplicate-case": 2, //switch中的case标签不能重复
// "no-dupe-args": 2, //函数参数不能重复
// "no-empty": 2, //块语句中的内容不能为空
// "no-func-assign": 2, //禁止重复的函数声明
// "no-invalid-this": 0, //禁止无效的this,只能用在构造器,类,对象字面量
// "no-redeclare": 2, //禁止重复声明变量
// "no-spaced-func": 2, //函数调用时 函数名与()之间不能有空格
// "no-this-before-super": 0, //在调用super()之前不能使用this或super
// "no-undef": 2, //不能有未定义的变量
// "no-use-before-define": 2, //未定义前不能使用
// // "camelcase": 0, //强制驼峰法命名
// "no-mixed-spaces-and-tabs": 0, //禁止混用tab和空格
// "prefer-arrow-callback": 0, //比较喜欢箭头回调
// "arrow-spacing": 0, //=>的前/后括号
//
// // 禁止在 componentDidMount 里面使用 setState
// // 禁止在 componentDidUpdate 里面使用 setState
// 'react/no-did-update-set-state': 2,
// // 禁止拼写错误
// 'react/no-typos': 2,
// // 禁止使用字符串 ref
// 'react/no-string-refs': 2,
// // @fixable 禁止出现 HTML 中的属性,如 class
// 'react/no-unknown-property': 2,
// // 禁止出现未使用的 propTypes
// // @off 不强制要求写 propTypes
// 'react/no-unused-prop-types': 2,
// // 出现 jsx 的地方必须 import React
// // @off 已经在 no-undef 中限制了
// 'react/react-in-jsx-scope': 0,
// // 非 required 的 prop 必须有 defaultProps
// // @off 不强制要求写 propTypes
// 'react/require-default-props': 0,
// // render 方法中必须有返回值
// 'react/require-render-return': 2,
// // @fixable 组件内没有 children 时,必须使用自闭和写法
// // @off 没必要限制
// 'react/self-closing-comp': 0,
// // style 属性的取值必须是 object
// 'react/style-prop-object': 2,
// // HTML 中的自闭和标签禁止有 children
// 'react/void-dom-elements-no-children': 2,
// // 数组中的 jsx 必须有 key
// 'react/jsx-key': 2,
// // 禁止在 jsx 中使用像注释的字符串
// 'react/jsx-no-comment-textnodes': 2,
// // 禁止出现重复的 props
// 'react/jsx-no-duplicate-props': 2,
// // 禁止使用未定义的 jsx elemet
// 'react/jsx-no-undef': 2,
// // jsx 文件必须 import React
// 'react/jsx-uses-react': 2,
// // 定义了的 jsx element 必须使用
// 'react/jsx-uses-vars': 2,
// // @fixable 多行的 jsx 必须有括号包起来
// // @off 没必要限制
// 'react/jsx-wrap-multilines': 2,
// "react/no-array-index-key": 2, // 遍历出来的节点必须加key
// "react/no-children-prop": 2, // 禁止使用children作为prop
// "react/no-direct-mutation-state": 2, // 禁止直接this.state = 方式修改state 必须使用setState
},
};
webpack.dev.js
const eslintFormatter = require('react-dev-utils/eslintFormatter');
... module: { rules: [
{
test: /\.(js|jsx)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: {
// 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: eslintFormatter, // 指定错误报告的格式规范
},
},
]
}, ...
根目录下新建 .eslintignore 文件 用来制定忽略某些文件的 eslint 校验
webpack postcss.config.js dist
### 3. stylelint
- 1. 下载依赖
npm i stylelint stylelint-config-recommended stylelint-config-standard stylelint-order stylelint-webpack-plugin -D
- 2. stylelint 配置
webpack.dev.js
const merge = require("webpack-merge"); const StyleLintPlugin = require("stylelint-webpack-plugin"); const common = require("./webpack.common.js"); module.exports = merge(common, { mode: "development", devtool: "inline-source-map", devServer: { contentBase: "/src", hot: true }, plugins: [
new StyleLintPlugin({
fix: true,
files: ["src/**/*.scss"],
failOnError: false,
quiet: true,
syntax: "scss",
cache: true
})
]
});
根目录下新建 .stylelintrc.js 文件
module.exports = { 'extends': [ 'stylelint-config-standard', 'stylelint-config-recommended', ], 'plugins': ['stylelint-order'], 'rules': { 'order/order': [ // "at-rules", // "declarations", 'custom-properties', 'dollar-variables', 'rules', ], 'order/properties-order': [ 'position', 'z-index', 'top', 'bottom', 'left', 'right', 'float', 'clear', 'columns', 'columns-width', 'columns-count', 'column-rule', 'column-rule-width', 'column-rule-style', 'column-rule-color', 'column-fill', 'column-span', 'column-gap', 'display', 'grid', 'grid-template-rows', 'grid-template-columns', 'grid-template-areas', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow', 'grid-column-gap', 'grid-row-gap', 'grid-template', 'grid-template-rows', 'grid-template-columns', 'grid-template-areas', 'grid-gap', 'grid-row-gap', 'grid-column-gap', 'grid-area', 'grid-row-start', 'grid-row-end', 'grid-column-start', 'grid-column-end', 'grid-column', 'grid-column-start', 'grid-column-end', 'grid-row', 'grid-row-start', 'grid-row-end', 'flex', 'flex-grow', 'flex-shrink', 'flex-basis', 'flex-flow', 'flex-direction', 'flex-wrap', 'justify-content', 'align-content', 'align-items', 'align-self', 'order', 'table-layout', 'empty-cells', 'caption-side', 'border-collapse', 'border-spacing', 'list-style', 'list-style-type', 'list-style-position', 'list-style-image', 'ruby-align', 'ruby-merge', 'ruby-position', 'box-sizing', 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'border', 'border-width', 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width', 'border-style', 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style', 'border-color', 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color', 'border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat', 'border-top', 'border-top-width', 'border-top-style', 'border-top-color', 'border-top', 'border-right-width', 'border-right-style', 'border-right-color', 'border-bottom', 'border-bottom-width', 'border-bottom-style', 'border-bottom-color', 'border-left', 'border-left-width', 'border-left-style', 'border-left-color', 'border-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-top-left-radius', 'outline', 'outline-width', 'outline-color', 'outline-style', 'outline-offset', 'overflow', 'overflow-x', 'overflow-y', 'resize', 'visibility', 'font', 'font-style', 'font-variant', 'font-weight', 'font-stretch', 'font-size', 'font-family', 'font-synthesis', 'font-size-adjust', 'font-kerning', 'line-height', 'text-align', 'text-align-last', 'vertical-align', 'text-overflow', 'text-justify', 'text-transform', 'text-indent', 'text-emphasis', 'text-emphasis-style', 'text-emphasis-color', 'text-emphasis-position', 'text-decoration', 'text-decoration-color', 'text-decoration-style', 'text-decoration-line', 'text-underline-position', 'text-shadow', 'white-space', 'overflow-wrap', 'word-wrap', 'word-break', 'line-break', 'hyphens', 'letter-spacing', 'word-spacing', 'quotes', 'tab-size', 'orphans', 'writing-mode', 'text-combine-upright', 'unicode-bidi', 'text-orientation', 'direction', 'text-rendering', 'font-feature-settings', 'font-language-override', 'image-rendering', 'image-orientation', 'image-resolution', 'shape-image-threshold', 'shape-outside', 'shape-margin', 'color', 'background', 'background-image', 'background-position', 'background-size', 'background-repeat', 'background-origin', 'background-clip', 'background-attachment', 'background-color', 'background-blend-mode', 'isolation', 'clip-path', 'mask', 'mask-image', 'mask-mode', 'mask-position', 'mask-size', 'mask-repeat', 'mask-origin', 'mask-clip', 'mask-composite', 'mask-type', 'filter', 'box-shadow', 'opacity', 'transform-style', 'transform', 'transform-box', 'transform-origin', 'perspective', 'perspective-origin', 'backface-visibility', 'transition', 'transition-property', 'transition-duration', 'transition-timing-function', 'transition-delay', 'animation', 'animation-name', 'animation-duration', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', 'animation-fill-mode', 'animation-play-state', 'scroll-behavior', 'scroll-snap-type', 'scroll-snap-destination', 'scroll-snap-coordinate', 'cursor', 'touch-action', 'caret-color', 'ime-mode', 'object-fit', 'object-position', 'content', 'counter-reset', 'counter-increment', 'will-change', 'pointer-events', 'all', 'page-break-before', 'page-break-after', 'page-break-inside', 'widows', ], 'indentation': 'tab', 'color-no-invalid-hex': true, 'font-family-no-missing-generic-family-keyword': null, 'font-family-name-quotes': null, 'function-url-quotes': 'always', 'at-rule-no-unknown': null, 'no-eol-whitespace': null, 'selector-attribute-quotes': 'always', 'string-quotes': 'single', 'selector-pseudo-element-colon-notation': null, 'at-rule-no-vendor-prefix': true, 'media-feature-name-no-vendor-prefix': null, 'media-feature-name-no-unknown': null, 'property-no-vendor-prefix': null, 'selector-no-vendor-prefix': true, 'value-no-vendor-prefix': true, 'selector-pseudo-class-no-unknown': null, 'shorthand-property-no-redundant-values': null, 'at-rule-empty-line-before': null, 'at-rule-name-space-after': null, 'comment-empty-line-before': null, 'declaration-bang-space-before': null, 'declaration-empty-line-before': null, 'function-comma-newline-after': null, 'function-name-case': null, 'function-parentheses-newline-inside': null, 'function-max-empty-lines': null, 'function-whitespace-after': null, 'number-leading-zero': null, 'number-no-trailing-zeros': null, 'rule-empty-line-before': null, 'selector-combinator-space-after': null, 'selector-list-comma-newline-after': null, // "selector-pseudo-element-colon-notation": null, 'unit-no-unknown': null, 'no-descending-specificity': null, 'value-list-max-empty-lines': null, }, };
## 十、 webpack DLL
> 在用 Webpack 打包的时候,对于一些不经常更新的第三方库,比如 react,lodash,我们并不希望每次打包都去编译他们,所以,应该只打包一次,然后多次使用,于是有了 DLL 的打包
下载依赖:
npm install webpack-bundle-analyzer --save-dev
- 1. 新建配置文件
webpack 文件夹下新建文件 `webpack.dll.config.js`
webpack.dll.config.js
const webpack = require('webpack'); const library = '[name]_lib'; const path = require('path'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); console.log('process.env.NODE_ENV>>>>', process.env.NODE_ENV) module.exports = { mode: 'production', entry: { vendors: [ 'react', '@babel/polyfill', 'react-dom', 'core-js', 'classnames' ], }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, './../ools'), library, }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, './../tools/[name]-manifest.json'), name: library, }), new BundleAnalyzerPlugin(), ], };
* 2 webpack.common.js 中配置
plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./tools/vendors-manifest.json'), }), ]
* 3. 新建 dll 脚本命令
"scripts": { ... "dll": "cross-env NODE_ENV=production webpack --config webpack/webpack.dll.config.js", ... },
执行 `npm run dll` 会在 tools 文件夹下生成对应的 dll 文件: `vendors-manifest.json` 和 `vendors.dll.js`,同时会自动打开浏览器查看到对应文件大小