从零开始撸一套react项目模板

863 阅读15分钟

1. 开发环境的依赖

  • git托管代码
  • 远程git仓库使用gitee.com
  • 使用yarn来安装相关依赖的包,yarn@1.22.1
  • webpack@4.39.2
  • webpack-cli@3.3.7
  • webpack-dev-server@3.8.0
  • node.js@12.16.1
  • node-sass@4.13.0

2. 初始化项目并关联远程git仓库

  • 在本地创建项目目录

进入目录,npm初始化项目

yarn init -y
  • 初始化git仓库

如下命令

#初始化项目
git init
echo "/node_modules\n/build" >> .gitignore
#关联远程仓库 
git remote add origin <url>

注1:项目关联远程仓库时如下报错

fatal: not a git repository (or any of the parent directories): .git

如下图

提示说没有.git这样一个目录,说不是一个git仓库,原来是没有初始化git项目,使用git init初始化一下就可以了.

注2:在完成项目关联远程仓库时,把项目推到远程仓库时报如下错误

error: src refspec master does not match any

如下图:

引起该错误的原因是,目录中没有文件,空目录是不能提交上去的。

原来我的本地项目没有Add 和commit,commit之后就可以把本地仓库的项目推到关联的远程仓库了

但如果是空目录想推项目打远程仓库的话,解决办法是

touch READMEgitadd README git commit -m '注释内容'git push origin master

3. webpack配置

3.1 基础配置设置

  • 在项目根目录下创建文件夹src,里面创建文件index.js作为webpack的入口文件

代码如下:

import React from 'react';
import reactDom from 'react-dom';

const App = () => (
  <div>
    test page
  </div>
);
reactDom.render(<App/>, document.getElementById('root'));

注意:创建js文件后,会显示格式错误,如下图

解决办法:

菜单栏 File=> settings => Languages & Frameworks => JavaScript => JavaScript language version

JavaScript 的版本设置为React JSX即可。

  • 创建模板文件/public/index.htmlwebpack打包后的文件将添加到该文件

代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
  • 创建 webpack 开发环境下配置文件 /webpack/webpack.config.dev.js

代码如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',                              
  entry: path.resolve(__dirname, '../src/index.js'),
  output: {                                         
    path: path.resolve(__dirname, '../build'),      
    filename: 'js/[name].[hash].bundle.js',         
  },
  module: {
    rules: [              
      {
        test: /\.(mjs|js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      }
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
   })
],

  resolve: {
    extensions: ['.mjs', '.js', '.jsx'],
  },
};
  • 创建 webpack 生产环境下配置文件 /webpack/webpack.config.prod.js

代码如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'production',                                
  entry: path.resolve(__dirname, '../src/index.js'),
  output: {                                         
    path: path.resolve(__dirname, '../build'),      
    filename: 'js/[name].[hash].bundle.js',         
  },
  module: {
    rules: [              
      {
        test: /\.(mjs|js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      }
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
    })
  ],
  resolve: {
    extensions: ['.mjs', '.js', '.jsx'],
  },
};

3.2 安装基础插件包

1. webpack 相关依赖包、插件

  • webpack: webpack 基础包

  • webpack-cli: webpack cli 工具包

  • html-webpack-plugin: webpack 插件, 用于将打包后的文件添加到指定的 html 内,即能生成单独的html文件,还可以对应的脚本和样式都自动的插入到合适的位置,不用手动的引入,还支持哈希的功能,防止在更新的时候有浏览器缓存

  • webpack-dev-server: webpack 开发环境工具, 创建一个开发环境,为webpack项目提供web服务的工具,能让我们在浏览器里通过http的形式访问编译好的文件

  • babel-loader: weboack loader, 用于编译打包 js 文件

  • @babel/core: babel 依赖包, 将 js 代码分析成 ast

  • @babel/preset-react: webpack react 相关预设

  • @babel/preset-env: weboack react 相关预设, 这样就可以使用最新的 js 相关语法

    yarn add webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader @babel/core @babel/preset-react @babel/preset-env -D

  • 创建 babel 配置文件 .babelrc

代码如下:

{
  "presets": [
    "@babel/preset-react",
    "@babel/preset-env"
  ]
}

2. react 相关依赖包、包

  • react
  • react-dom

安装命令如下:

yarn add react react-dom -S

3. 修改package.json的script命令

  • 修改 package.json: 添加 npm 脚本,dev启动命令,build生产环境打包命令

内容如下:

"scripts": {
    "dev": "webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
    "build": "webpack --config ./webpack/webpack.config.prod.js"
}

3.3 html-webpack-plugin简单使用

1.template使用,我们可以指定html模板

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
    })
  ]
};

2.title使用,我们可以指定模板title

在指定html模板内的代码如下

<title><%= htmlWebpackPlugin.options.title %></title>

webpack的配置文件的代码如下

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: '测试',
      template: path.resolve(__dirname, '../public/index.html'),
    })
  ]
};

3. **filename**的使用,代码如下

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: '测试',
      template: path.resolve(__dirname, '../public/index.html'),
      filename: 'index.html' //打包后的文件名
    })
  ]
};

4. html-webpack-plugin 配置文件 config 的灵活使用

有时候,我们的脚手架不仅仅给自己使用,也许还提供给其它业务使用,html 文件的可配置性可能很重要,比如:你公司有专门的部门提供M页的公共头部/公共尾部,埋点jssdk以及分享的jssdk等等,但是不是每个业务都需要这些内容。

一个功能可能对应多个 js 或者是 css 文件,如果每次都是业务自行修改 public/index.html 文件,也挺麻烦的。首先他们得搞清楚每个功能需要引入的文件,然后才能对 index.html 进行修改。

此时我们可以增加一个配置文件,业务通过设置 truefalse 来选出自己需要的功能,我们再根据配置文件的内容,为每个业务生成相应的 html 文件。

首先,我们在 public 目录下新增一个 config.js ( 文件名你喜欢叫什么就叫什么 ),将其内容设置为:

//public/config.js 除了以下的配置之外,这里面还可以有许多其他配置,例如,pulicPath 的路径等等
module.exports = {
    dev: {
        template: {
            title: '测试1',
            header: false,
            footer: false
        }
    },
    build: {
        template: {
            title: '测试2',
            header: true,
            footer: false
        }
    }
}

现在,修改下 webpack.config.dev.jswebpack.config.prod.js:

//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
const config = require('./public/config')[isDev ? 'dev' : 'build'];

modue.exports = {
    //...
    mode: isDev ? 'development' : 'production'
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html', //打包后的文件名
            config: config.template
        })
    ]
}

相应的,我们需要修改下我们的 public/index.html 文件(js和css仅作为示意):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <% if(htmlWebpackPlugin.options.config.header) { %>
    <link rel="stylesheet" type="text/css" href="//common/css/header.css">
    <% } %>
    <title><%= (htmlWebpackPlugin.options.config.title) %></title>
</head>

<body>
</body> 
<% if(htmlWebpackPlugin.options.config.header) { %>
<script src="//common/header.min.js" type="text/javascript"></script> 
<% } %>
</html>

注意process.env 中默认并没有 NODE_ENV,这里配置下我们的 package.jsonscripts

为了兼容Windows和Mac,我们先安装一下 cross-env

yarn add cross-env -D


{
    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
        "build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js",
     }
}

注意:在此,我们配置了开发环境的webpack配置文件,与生产环境的webpack配置文件

我们运行 yarn run dev跟运行 yarn run build,对比 build/index.html,可以看到 yarn run build生成的文件index.html,文件中引入了对应css和js。并且对应title内容也不一样。

3.4 测试

1. 执行 yarn run dev测试项目是否能够正常运行

2. 执行 yarn run build 测试是否能够正常对项目进行打包、编译, 编译后目录结构如下

.
├── index.html
└── js
    └── main.0b16f9b82b7fb2c9ba57.bundle.js

注意:注意webpack-dev-server的版本兼容问题,我webpack版本是"webpack": "4.39.2",webpack-cli版本是"webpack-cli": "3.3.7",webpack-dev-server版本是"webpack-dev-server": "3.8.0",能够正常启动服务,但又会出现下面报错:

TypeError: Cannot read property 'headers' of null

如下图报错:

webpack-dev-server版本改为"webpack-dev-server": "3.7.0",则不会再出现以上错误。

4. 处理css/scss样式文件

4.1 安装相关依赖

webpack不能直接处理css,需要借助loader。如果是.css,我们需要的loader通常有:style-loader、css-loader考虑到兼容性问题,还需要postcss-loader,而如果是less或者是sass的话,还需要less-loader和sass-loader,这里配置一下sass和css文件(less的话,使用less-loader即可):

安装相关依赖:

  • css-loader、sass-loader  处理css样式和scss样式
  • style-loader    css-loader、sass-loader处理的样式结果通过style-loader处理成DOM里的style标签
  • mini-css-extract-plugin  抽离CSS,即将CSS文件单独打包,这可能是因为打包成一个JS文件太大,影响加载速度,也有可能是为了缓存,使用该插件的,则会替代style-loader的功能

命令如下:

yarn add style-loader sass-loader css-loader mini-css-extract-plugin node-sass -D

4.2 webpack配置修改

webpack代码配置如下:

//webpack.config.dev.js
// 将CSS文件单独打包
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.(sc|c)ss$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,                        
                        options: {
                            hmr: process.env.NODE_ENV === 'development'
                        }
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1
                        }
                    },
                    'sass-loader'
                ],
                exclude: /node_modules/
            }
        ]
    }
}

注意mini-css-extract-plugin版本兼容配置问题,如果webpack的配置文件在mini-css-extract-plugin的配置方式如下:

而安装的mini-css-extract-plugin版本为"mini-css-extract-plugin": "^1.3.1"

则启动服务后会报如下错误:

把mini-css-extract-plugin的配置方式如下:

启动服务则会报如下错误:

以上问题是mini-css-extract-plugin版本不兼容问题,使用"mini-css-extract-plugin": "1.2.1"版本,则不会出现以上问题。

5. 图片/字体文件处理

使用url-loader处理图片/字体,url-loader依赖file-loader,url-loader 可以指定在文件大小小于指定的限制时,返回 DataURL

5.1 webpack配置文件的修改

//webpack.config.dev.jsmodule.exports = {
  module: {
    rules: [              
     {
       test: /\.(png|jpg|gif|woff|svg|eot|ttf)$/,
       use: [{
         loader: 'url-loader',
         options: {
           limit: 10000,
           esModule: false
           name: '[name]_[hash:6].[ext]',
           outputPath: 'assets'
         },
       }],
       exclude: /node_modules/
     },
    ],
  },
};

5.2安装相关依赖

安装命令如下:

yarn add url-loader file-loader -D

注意:如果项目运行或者打包后,图片文件路径错误输出为[object-module],则需要设置esModule: false

6. 更多相关webpack配置

6.1 每次打包前清空build目录

1. 安装依赖:

yarn add clean-webpack-plugin -D

2. webpack配置文件代码如下:

//webpack.config.dev.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    //...
    plugins: [
        //不需要传参数,它可以找到 outputPath
        new CleanWebpackPlugin() 
    ]
}

3. 希望build目录下某个文件夹不被清空

clean-webpack-plugin 为我们提供了参数 cleanOnceBeforeBuildPatterns

webpack配置文件代码如下:

//webpack.config.dev.js
module.exports = {
    //...
    plugins: [
        new CleanWebpackPlugin({
            cleanOnceBeforeBuildPatterns:['**/*', '!dll', '!dll/**'] //不删除dll目录下的文件
        })
    ]
}

6.2 静态资源拷贝到public

场景:有些时候,我们需要使用已有的JS文件、CSS文件(本地文件),但是不需要 webpack 编译。例如,我们在 public/index.html 中引入了 public 目录下的 jscss 文件。这个时候,如果直接打包,那么在构建出来之后,肯定是找不到对应的 js / css 。

对于这个问题,我们可以通过copy-webpack-plugin拷贝至构建目录,然后在配置 CleanWebpackPlugin 时,注意不要清空对应的文件或文件夹即可。

1. 安装依赖

yarn add copy-webpack-plugin -D

2. webpack配置文件代码如下:

//webpack.config.dev.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
    //...
    plugins: [
        new CleanWebpackPlugin(
            cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'build', 'js')],        
        ),
        new CopyWebpackPlugin([
            {
                from: 'public/js/*.js',
                to: path.resolve(__dirname, 'build', 'js'),
                flatten: true,
            },
            //还可以继续配置其它要拷贝的文件
        ])
    ]
}

注意flatten 这个参数,设置为 true,那么它只会拷贝文件,而不会把文件夹路径都拷贝上

3. 如果我们要拷贝一个目录下的很多文件,但是想过滤掉某个或某些文件,那么 CopyWebpackPlugin 还为我们提供了 ignore 参数。

webpack配置文件代码如下:

//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
    //...
    plugins: [
        new CopyWebpackPlugin([
            {
                from: 'public/js/*.js',
                to: path.resolve(__dirname, 'build', 'js'),
                flatten: true,
            }
        ], {
            ignore: ['other.js']
        })
    ]
}

6.3 自动加载模块之ProvidePlugin

ProvidePlugin 的作用就是不需要 importrequire 就可以在项目中到处使用。

ProvidePluginwebpack 的内置插件,使用方式如下:

new webpack.ProvidePlugin({
  identifier1: 'module1',
  identifier2: ['module2', 'property2']
});

默认寻找路径是当前文件夹 ./**node_modules,你可以指定全路径。

1. webpack配置代码如下:

const webpack = require('webpack');
module.exports = {
    //...
    plugins: [
        new webpack.ProvidePlugin({
            React: 'react',
            Component: ['react', 'Component'],
            Vue: ['vue/dist/vue.esm.js', 'default'],
            $: 'jquery',
            _map: ['lodash', 'map']
        })
    ]
}

这样配置之后,你就可以在项目中随心所欲的使用 $_map了,并且写 React 组件时,也不需要 import ReactComponent 了,如果你想的话,你还可以把 ReactHooks 都配置在这里。

注意Vue 的配置后面多了一个 default,这是因为 vue.esm.js 中使用的是 export default 导出的,对于这种,必须要指定 defaultReact 使用的是 module.exports 导出的,因此不要写 default

2. 全局变量定义在eslint相关配置文件的设置

如果你项目启动了 eslint 的话,记得修改下 eslint 的配置文件,增加以下配置:

.eslintrc.js配置文件的设置:

{
    "globals": {
        "React": true,
        "Vue": true,
        //....
    }
}

6.4 定义全局变量之DefinePlugin

DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。DefinePluginwebpack的内置插件。

1. 使用场景:

  • 如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。

  • 我们在开发环境中会使用预发环境或者是本地的域名,生产环境中使用线上域名,我们可以在 webpack 定义环境变量,然后在代码中使用。

2. 用法

每个传进 DefinePlugin 的键值都是一个标志符或者多个用 . 连接起来的标志符。

  • 如果 value 是一个字符串,它会被当做 code 代码片段
  • 如果 value 不是一个字符串,它会被stringify,会被转化为字符串(包括函数)
  • 如果 value 是一个对象,正常对象定义即可。即它所有的 key 会被同样的方式定义
  • 如果 key 中有 typeof,它只针对 typeof 调用定义。即如果在一个 key 前面加了 typeof,它会被定义为 typeof 调用

3. 代码使用如下:

+ const { DefinePlugin } = require('webpack');

+ const definePlugin = new DefinePlugin({
+   _DEV_: false,
+   GLOBAL_SERVICE: {
+     HOST: JSON.stringify('https://www.qianyin925.com:4000'),
+     GRAPHQL_URL: JSON.stringify('/graphql'),
+   },
+ });

module.exports = {
  plugins: [
+   definePlugin,
  ]
};

6.5 resolve 配置

resolve 的作用:配置 webpack 如何寻找模块所对应的文件。

webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你可以根据自己的需要修改默认的规则。

1. module

resolve.modules 配置 webpack 去哪些目录下寻找第三方模块,默认情况下,只会去 node_modules 下寻找,如果你我们项目中某个文件夹下的模块经常被导入,不希望写很长的路径,那么就可以通过配置 resolve.modules 来简化。

//webpack.config.dev.js
module.exports = {
    //....
    resolve: {
        modules: ['./src/components', 'node_modules'] //从左到右依次查找
    }
}

这样配置之后,我们 import Dialog from 'dialog',会去寻找 ./src/components/dialog,不再需要使用相对路径导入。如果在 ./src/components 下找不到的话,就会到 node_modules 下寻找。

2. alias

resolve.alias 配置项通过别名把原导入路径映射成一个新的导入路径,例如:

//webpack.config.dev.js
module.exports = {
    //....
    resolve: {
        alias: {
            "@": path.resolve(__dirname, '../src'), 
            'react-native': '@my/react-native-web' //这个包名是随便写的
        }
    }
}

3. extensions

适配多端的项目中,可能会出现 .web.js, .wx.js,例如在转web的项目中,我们希望首先找 .web.js,如果没有,再找 .js。我们可以这样配置:

//webpack.config.dev.js
module.exports = {
    //....
    resolve: {
        extensions: ['web.js', '.js'] //当然,你还可以配置 .json, .css
    }
}

例如引入以下代码:

import dialog from '../dialog';

首先寻找 ../dialog.web.js ,如果不存在的话,再寻找 ../dialog.js。这在适配多端的代码中非常有用,否则,你就需要根据不同的平台去引入文件(以牺牲了速度为代价)。

当然,配置extensions我们就可以缺省文件后缀,在导入语句没带文件后缀时,会自动带上extensions 中配置的后缀后,去尝试访问文件是否存在,因此要将高频的后缀放在前面,并且数组不要太长,减少尝试次数。如果没有配置 extensions,默认只会找对对应的js文件。

4. enforceExtension

如果配置了 resolve.enforceExtensiontrue,那么导入语句不能缺省文件后缀。

5. mainFields

有一些第三方模块会提供多份代码,例如 bootstrap,可以查看 bootstrappackage.json 文件:

{
    "style": "dist/css/bootstrap.css",
    "sass": "scss/bootstrap.scss",
    "main": "dist/js/bootstrap",
}

resolve.mainFields 默认配置是 ['browser', 'main'],即首先找对应依赖 package.json 中的 brower 字段,如果没有,找 main 字段。

如:import 'bootstrap' 默认情况下,找得是对应的依赖的 package.jsonmain 字段指定的文件,即 dist/js/bootstrap

但假设我们希望,import 'bootsrap' 默认去找 css 文件的话,可以配置 resolve.mainFields 为:

//webpack.config.dev.js
module.exports = {
    //....
    resolve: {
        mainFields: ['style', 'main'] 
    }
}

6.6 cross-env 设置环境变量

优点: 兼容多个平台

1. package.json的script命令设置如下:

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
    "build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js"  }
}

2. 安装命令如下:

yarn add cross-env -D

6.7 raw-loader 用于加载文本内容(.txt、.md 文件)

一个可以用于加载文件作为字符串使用的加载器,使用UTF-8编码。

1. 安装命令如下:

yarn add raw-loader -D

2. webpack配置文件如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(text|md)$/,
        use: 'raw-loader',
      },
    ]
  }
};

引入方式如下:

例如在file.js文件里

import txt from './file.txt';

6.8 区分不同的环境之webpack-merge使用

对于webpack.config.js配置文件,当我们需要区分是开发环境还是生产环境的情况时,好的做法是创建多个配置文件,如: webpack.config.base.jswebpack.config.dev.jswebpack.config.prod.js

  • webpack.config.base.js 定义公共的配置
  • webpack.config.dev.js:定义开发环境的配置
  • webpack.config.prod.js:定义生产环境的配置

webpack-merge 专为 webpack 设计,提供了一个 merge 函数,用于连接数组,合并对象。

1. webpack-merge安装命令如下:

yarn add webpack-merge -D

2. webpack-merge的作用如下示例:

const merge = require('webpack-merge');
merge({
    devtool: 'cheap-module-eval-source-map',
    module: {
        rules: [
            {a: 1}
        ]
    },
    plugins: [1,2,3]
}, {
    devtool: 'none',
    mode: "production",
    module: {
        rules: [
            {a: 2},
            {b: 1}
        ]
    },
    plugins: [4,5,6],
});
//合并后的结果为
{
    devtool: 'none',
    mode: "production",
    module: {
        rules: [
            {a: 1},
            {a: 2},
            {b: 1}
        ]
    },
    plugins: [1,2,3,4,5,6]
}

其中,webpack.config.base.js 中是通用的 webpack 配置。

webpack.config.dev.js 为例,如下:

//webpack.config.dev.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');

module.exports = merge(baseWebpackConfig, {
    mode: 'development'
    //...其它的一些配置
});

对应 package.json的script命令修改如下:

//package.json
{
    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
        "build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js"
    },
}

你可以使用 merge 合并,也可以使用 merge.smart 合并,merge.smart 在合并loader时,会将同一匹配规则的进行合并,webpack-merge 的说明文档中给出了详细的示例。

6.9 webpack解决跨域问题

假设前端在3000端口,服务端在4000端口,我们通过 webpack 配置的方式去实现跨域。

1. 在 localhost:4000 上有后端服务的话,你可以这样启用代理:

proxy: {
  "/api": "http://localhost:3000"
}

请求到 /api/users 现在会被代理到请求 http://localhost:4000/api/users

2. 如果你不想始终传递 /api ,则需要重写路径:

proxy: {
  "/api": {
    target: "http://localhost:3000",
    pathRewrite: {"^/api" : ""}
  }
}

3. 默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置如下:

proxy: {
  "/api": {
    target: "https://other-server.example.com",
    secure: false
  }
}

4. proxy的参数列表中有一个changeOrigin参数, 是一个布尔值, 设置为true, 本地就会虚拟一个服务器接收你的请求并代你发送该请求

proxy: {
  "/api": {
    target: "https://other-server.example.com",
    secure: false,
    changeOrigin: true
  }
}

6.10 热更新

1. 首先配置 devServerhottrue

2. 并且在 plugins 中增加 new webpack.HotModuleReplacementPlugin()

代码如下:

//webpack.config.dev.js
const webpack = require('webpack');
module.exports = {
    //....
    devServer: {
        hot: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin() //热更新插件
    ]
}

6.11 多页应用打包方式

1. 场景:我们的应用不一定是一个单页应用,而是一个多页应用,那么如何使用 webpack 进行打包呢。

为了生成目录看起来清晰,不生成单独的 map 文件。

代码如下:

//webpack.config.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: {
        index: './src/index.js',
        login: './src/login.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash:6].js'
    },
    //...
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html' //打包后的文件名
        }),
        new HtmlWebpackPlugin({
            template: './public/login.html',
            filename: 'login.html' //打包后的文件名
        }),
    ]
}

注意:如果需要配置多个 HtmlWebpackPlugin,那么 filename 字段不可缺省,否则默认生成的都是 index.html,如果你希望 html 的文件名中也带有 hash,那么直接修改 fliename 字段即可,例如: filename: 'login.[hash:6].html'

2. 生成目录如下:

.
├── build
│   ├── 2.463ccf.js
│   ├── assets
│   │   └── thor_e09b5c.jpeg
│   ├── css
│   │   ├── index.css
│   │   └── login.css
│   ├── index.463ccf.js
│   ├── index.html
│   ├── js
│   │   └── base.js
│   ├── login.463ccf.js
│   └── login.html

但在查看 index.htmllogin.html 会发现,都同时引入了 index.f7d21a.jslogin.f7d21a.js,通常这不是我们想要的,我们希望,index.html 中只引入 index.f7d21a.jslogin.html 只引入 login.f7d21a.js

HtmlWebpackPlugin提供了一个 chunks 的参数,可以接受一个数组,配置此参数仅会将数组中指定的js引入到html文件中,此外,如果你需要引入多个JS文件,仅有少数不想引入,还可以指定 excludeChunks 参数,它接受一个数组。

代码如下:

//webpack.config.dev.js
module.exports = {
    //...
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html', //打包后的文件名
            chunks: ['index']
        }),
        new HtmlWebpackPlugin({
            template: './public/login.html',
            filename: 'login.html', //打包后的文件名
            chunks: ['login']
        }),
    ]
}

6.12 devtool

devtool 中的一些设置,可以帮助我们将编译后的代码映射回原始源代码。不同的值会明显影响到构建和重新构建的速度。

对我而言,能够定位到源码的行即可,因此,综合构建速度,在开发模式下,我设置的 devtool 的值是 cheap-module-eval-source-map

//webpack.config.js
module.exports = {
    devtool: 'cheap-module-eval-source-map' //开发环境下使用
}

7. 优化项目的webpack配置

webpack打包优化的方式:多进程打包多进程压缩资源CDN动态polyfill

7.1 量化指标插件之speed-measure-webpack-plugin

speed-measure-webpack-plugin 插件可以测量各个插件和loader所花费的时间,即速度测量插件,使用之后,构建时,会得到类似下面这样的信息:

这样可以对比前后的信息,来确定优化的效果。

安装命令如下:

yarn add speed-measure-webpack-plugin -D

speed-measure-webpack-plugin的配置信息如下:

//webpack.config.dev.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

const config = {
    //...webpack配置
}

module.exports = smp.wrap(config);

如果使用webpack-merge这个插件区分了不同的环境,则speed-measure-webpack-plugin的配置信息如下:

//webpack.config.base.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

const config = {
    //...webpack配置
}

module.exports = smp.wrap(config);

7.2 体积分析之webpack-bundle-analyzer

分析完打包速度之后,接着我们来分析打包之后每个文件以及每个模块对应的体积大小。使用到的插件为 webpack-bundle-analyzer,构建完成后会在 8888 端口展示大小。

借助 webpack-bundle-analyzer 查看一下是哪些包的体积较大。该依赖是在项目打包时使用。

1. 安装依赖命令:

yarn add webpack-bundle-analyzer -D

2. 配置文件代码配置如下:

//webpack.config.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
    //....
    plugins: [
        //...
        new BundleAnalyzerPlugin(),
    ]
})

3. 运行yarn run build命令后打包自动启动http://127.0.0.1:8888/端口,显示如下图:

7.3 webpack打包速度优化

1. 优化场景:

webpack构建过程中直接影响构建效率的,一个是文件的编译,另一个是文件的分类打包。相较之下文件的编译更为耗时,而且在Node环境下文件只能一个一个去处理,因此这块的优化需要解决。那么要怎样优化打包速度呢?

2. 使用高版本的webpacknode.js

webpack4新版本的优化使用v8引擎,v8带来的优化包括:

  • for of替代forEach
  • Map和Set替代Object
  • includes替代indexOf()
  • 默认使用更快的md4 hash算法 替代 md5算法,md4较md5速度更快
  • webpack AST 可以直接从loader传递给AST,从而减少解析时间
  • 使用字符串方法替代正则表达式

更高版本的node.js对原生js apijs数据结构做出进一步的优化

3. 多进程/多实例构建(资源并行解析)

在webpack构建过程中,我们需要使用Loader对js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的,且这些转换操作不能并发处理文件,而是需要一个个文件进行处理,我们需要的**是将这部分任务分解到多个子进程中去并行处理**,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。

可选方案

  • thread-loader(官方推出)
  • parallel-webpack
  • HappyPack

HappyPack

注意由于HappyPack作者对js的兴趣逐渐减少,所以之后维护将变少,webpack4及之后推荐使用thread-loader

原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker进程中;HappyPack会将模块进行一个划分,比如我们有多个模块,这些模块交给HappyPack,首先在webpack compiler(钩子)的run方法之后,进程就会到达HappyPack,**HappyPack会做一些初始化**,初始化之后会**创建一个线程池**,线程池会将构建任务里面的模块进行一个分配,比如会将某个模块以及它的一些依赖分配给其中的一个HappyPack线程,以此类推,那么一个HappyPack的**一个线程池会包括多个线程,这时候线程池的这些线程会各自去处理其中的模块以及它的依赖,处理完成之后会有一个通信的过程,会将处理好的资源传输给HappyPack的一个_主进程_**,完成整个的一个构建过程。

将src目录下复制出多个相同页面

在没引入HappyPack之前执行打包

HappyPack安装命令如下:

yarn add happypack -D

注意:如果在webpack4使用需要HappyPack5.0的版本。

引入之后将rules对js的编译改为happypack/loader

webpack文件设置如下

rules: [
      {
        test: /\.(mjs|js|jsx)$/,        use: 'HappyPack/loader?id=js',
        exclude: /node_modules/
      },
]

在插件中加入happypack-loader

plugins: [
    new HappyPack({
        id: 'js', //和rule中的id=js对应
        threadPool: HappyPack.ThreadPool({size: 4}),
        loader: [
            {
                loader: 'babel-loader',                
                options: {
                    presets: ["@babel/preset-env", "@babel/preset-react"],
                    plugins: [
                        [
                            "@babel/plugin-transform-runtime",
                            {"corejs": 3}
                        ]
                    ]
                }
            }
        ]
    })
]

thread-loader

原理:与HappyPack类似,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker进程中;

安装命令如下:

yarn add thread-loader -D

在rule中添加thread-loader,thread-loader可以进行一些配置,例如workers(进程数)

rules: [
      {
        test: /\.(mjs|js|jsx)$/,        
        use: {
            'babel-loader',
            {
                loader: 'thread-loader',
                options: {
                    workers: 3
                }
            }
        },
        exclude: /node_modules/
      },
]

7.4 多进程/多实例进行代码压缩(并行压缩)

在代码构建完成之后输出之前有个代码压缩阶段,这个阶段也可以进行并行压缩来达到优化构建速度的目的;

可选方案:

  • webpack-parallel-uglify-plugin
  • uglifyjs-webpack-plugin(不支持es6语法)
  • terser-webpack-plugin**(webpack4.0推荐使用,支持压缩es6代码)**

引入代码如下:

const TerserPlugin = require('terser-webpack-plugin');

optimization配置如下:

optimization: {
  minimize: true,
  minimizer: [
    new TerserPlugin({
      //代码压缩插件
      parallel: 4, //开启并行压缩
    }),
  ],
},

注意:在webpack配置文件设置好后,启动服务,会报

TypeError: Cannot read property 'javascript' of undefined

错误提示,原来是版本问题

如果是使用webpack4版本,则可以对应安装"terser-webpack-plugin": "^4.2.3",webpack5使用terser-webpack-plugin5.0以上版本。我现在安装的webpack版本是"webpack": "4.39.2"

则对应terser-webpack-plugin版本可以安装"terser-webpack-plugin": "^4.2.3",如果安装"terser-webpack-plugin": "^5.0.3",则报如下图错误:

7.5 通过缓存提升二次打包速度

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用cache-loader或者 hard-source-webpack-plugin

1. 设置babel-loader的cacheDirectory=true开启缓存

new HappyPack({
  loaders: ['babel-loader?cacheDirectory=true'],
})

2. 设置terser-webpack-plugin插件的cache: true开启缓存

  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        //代码压缩插件
        parallel: 4, //开启并行压缩
        cache: true,
      }),
    ],
  },

3. HardSourceWebpackPlugin之为模块提供中间缓存

HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source

配置 hard-source-webpack-plugin,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。

安装命令如下:

yarn add hard-source-webpack-plugin -D

webpack配置文件修改如下:

//webpack.config.base.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
    //...
    plugins: [
        new HardSourceWebpackPlugin()
    ]
}

更改内容后第一次打包时间如下(还是比没加缓存前打包速度快):

不更改内容再次打包的时间如下:

7.5 缩小构建目标

尽可能的少构建模块,比如babel-loader不解析 node_modules

  • 优化resolve.modules配置(减少模块搜索层级)
  • 优化resolve.mainFields配置
  • 优化resolve.extensions配置

7.6 打包体积的优化

主要对打包后图片、js、css文件的资源体积优化

1. 图片压缩

使用Node库的imagemin,配置image-webpack-loader对图片优化,改插件构建时会识别图片资源,对图片资源进行优化

imagemin优点分析

  • imagemin有很多定制选项
  • 可以引入更多第三方优化插件,例如pngquant
  • 可以引入多种图片格式

imagemin的压缩原理

  • pngquant:是一款PNG的压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60%-80%)的更高效的8位PNG格式,可显著减小文件大小;
    阿尔法通道(Alpha Channel)是指一张图片的透明和半透明度;

  • pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小;

  • optipng:其涉及灵感来自于pngcrush。optipng可将图像文件重新压缩位更小的尺寸,而不会丢失任何信息;

  • tingpng:也是将24位png文件转化为更小具有索引的8位图片,同时所有非必要的metadata也会被剥离掉;

安装命令如下:

yarn add image-webpack-loader -D

注意:该插件在使用yarn或者npm即使使用梯子也会安装报错,建议使用cnpm安装。

webpack配置文件如下:

rules: [
      {
        test: /.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]',
            },
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 65,
              },
              // optipng.enabled: false will disable optipng
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.65, 0.9],
                speed: 4,
              },
              gifsicle: {
                interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75,
              },
            },
          },
        ],
      }
]

对比使用image-webpack-loader插件的前后对比如下:

使用前:

使用依赖后:

对比使用image-webpack-loader前后的文件大小,可显著减小文件大小,把大文件的图片压缩。

注意:注意依赖的安装顺序,如果先安装好webpack-dev-server以后安装image-webpack-loader,不会出现什么问题,但如果两者都安装好再remove掉webpack-dev-server后再安装webpack-dev-server,启动服务,会报image-webpack-loadermozjpeg、gifsicle等相关的错误,必须remove掉image-webpack-loader,重新再安装就好了,不会再报错。

如果出现image-webpack-loader安装不成功的情况,则使用 cnpm , 这一步意思就是安装 cnpm 然后将全局的 registry 设置成阿里的镜像,国内阿里比较快。

命令如下:

npm install cnpm -g --registry=https://registry.npm.taobao.org

使用 cnpm 安装 image-webpack-loader 会发现很快就安装好了

cnpm install --save-dev  image-webpack-loader

注意:如果先前尝试使用 yarnnpm 安装过image-webpack-loader ,一定要先remove卸载掉用再使用 cnpm 安装。

2. 擦除没有用到的css

可以通过插件遍历代码,识别已经用到的css class

安装命令如下:

yarn add purgecss-webpack-plugin -D

注意:该插件在使用yarn或者npm即使使用梯子也会安装报错,建议使用cnpm安装。

webpack配置文件如下:

const path = require('path');
const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS = {
    src: path.join(__dirname, 'src')
}

plugins: [
    new PurgecssPlugin({
        paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
    })
]

设置多个路径:

如果你需要设置多个路径,请使用 npm 软件包 glob-all 来替换 glob,然后就可以使用以下语法了:

new PurgecssPlugin({
  paths: glob.sync([
    // ...
  ])
}),

7.7 babel-polyfill的使用

8. 引入 Antd 并配置按需加载

.babelrc 配置添加插件 babel-plugin-import 从而实现 antd 的按需加载。

8.1 修改.babelrc

注意: 配置插件时可以设置实例化名称 import-antd, 这样就可以多次使用同一插件, 如果你还需要使用 babel-plugin-import 处理其他组件库

{
+ "plugins": [
+   ["import", {
+     "libraryName": "antd",
+     "libraryDirectory": "es",
+     "style": "css"
+   }, "import-antd"]
+ ],
  "presets": [
    "@babel/preset-react",
    "@babel/preset-env"
  ]
}

8.2 相关依赖安装

yarn add antd
yarn add babel-plugin-import -D

8.3 代码引入示例

import React from 'react';
import reactDom from 'react-dom';
+ import { Button } from 'antd';

const App = () => (
  <div>
+   <Button type="primary">按钮</Button>
  </div>
);
reactDom.render(<App/>, document.getElementById('root'));

注意:在引入antd后运行项目报如下错误:

ERROR in ./node_modules/antd/es/button/style/index.css 5:0
Module parse failed: Unexpected token (5:0)

原因如下:在webpack配置文件里css的配置部分多了exclude: /node_modules/,这样导致编译css文件时不会编译antd部分,导致报错,如下图: