搭建本身的React+Typescript环境(二)

前言

上一篇文章介绍了React+Typescript的基础环境搭建,并无作任何优化配置,以及根据不一样的开发环境拆分配置,这篇文章主要就是介绍这些,而且全部配置都是在上篇文章的基础上,若是有什么问题或者不对的地方,但愿大佬们能及时指出,最后有项目地址~css

要用到的几个依赖

  • webpack-merge:合并webpack配置
  • webpack.DefinePlugin:在编译时建立一些全局变量
  • webpack.HotModuleReplacementPlugin:用于启用局部模块热重载,开发环境用的
  • html-webpack-plugin:根据webpack打包生成的bundle,来生成html
  • add-asset-html-webpack-plugin:跟html-webpack-plugin配合使用,把资源文件引用到它生成的html中
  • mini-css-extract-plugin:把css抽取到不一样的文件中
  • terser-webpack-plugin:新的压缩js代码插件
  • optimize-css-assets-webpack-plugin:在webpack打包时优化压缩css代码,主要使用 cssnano 压缩器。
  • webpack.runtimeChunk:与持久化缓存有关
  • webpack.splitChunks:webpack 4 最大的改动就是废除了 CommonsChunkPlugin 引入了 optimization.splitChunks,用来配置分包策略。
  • webpack.DllPlugin:将模块预先编译,它会在第一次编译的时候将配置好的须要预先编译的模块编译在缓存中,第二次编译的时候,解析到这些模块就直接使用缓存
  • webpack.DllReferencePlugin:将预先编译好的模块关联到当前编译中,当 webpack 解析到这些模块时,会直接使用预先编译好的模块
  • webpack-bundle-analyzer:webpack打包分析器,能够直观看到各bundle占比
  • clean-webpack-plugin:清理打包文件夹

公共配置

在上篇webpack.common.js中继续添加和更新咱们的配置。html

定义可能用到的全局变量

有的时候须要在不一样的环境定义不一样的变量,就像vue-cli3建立的项目中的.env文件同样。vue

首先在 build 文件夹下新建一个 env.json 文件夹,并在里面写上你可能用到的全局变量。node

{
  "dev": {
    "APP_ENVO": "dev",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "test": {
    "APP_ENVO": "test",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "pre": {
    "APP_ENVO": "pre",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "prod": {
    "APP_ENVO": "prod",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  }
}
复制代码

接下来须要用到 yargs-parser 这个插件,yargs-parser: 用于将咱们的npm scripts中的命令行参数转换成键值对的形式如 --mode development会被解析成键值对的形式mode: "development",便于在配置文件中获取参数。react

而后在 package.json 中的scripts 脚本中加上咱们的环境参数 --env test 等,例如:webpack

"scripts": {
    "dev": "webpack-dev-server --config build/webpack.dev.js --mode development --open",
    "test-build": "webpack --config build/webpack.prod.js --mode production --env test",
    "pre-build": "webpack --config build/webpack.prod.js --mode production --env pre",
    "prod-build": "webpack --config build/webpack.prod.js --mode production --env prod"
  },
复制代码

而后在 webpack.common.js 中拿到这个参数,并利用 webpack.DefinePlugin 这个插件将这些变量配置进去git

const argv = require('yargs-parser')(process.argv.slice(4))
const APP_ENV = argv.env || 'dev'

const env = require('./env.json')
const oriEnv = env[config.APP_ENV]
Object.assign(oriEnv, {
	APP_ENV: config.APP_ENV
})

const defineEnv = {}
for (let key in oriEnv) {
	defineEnv[`process.env.${key}`] = JSON.stringify(oriEnv[key])
}

module.exports={
  // ... 省略了其余配置
  plugins: [
    new webpack.DefinePlugin(defineEnv)
  ]
}

复制代码

以后在项目启动后就能够经过 process.env.${key} 对应的键,拿到相应的值了。github

修改输出 output

修改咱们打包后的 js 输出目录以及名称,让它看起来清晰一些。web

module.exports={
  output: {
    filename: 'js/[name].[chunkhash].js',
    path: path.join(__dirname, '../dist')
  }
}
复制代码

开发环境配置

首先在 build 下新建一个 webpack.dev.js,而后须要安装 webpack-merge 来合并配置。vuex

yarn add webpack-merge -D
复制代码

接下来引入它以及公共配置文件,把以前的 devServer 移到这里,并引入 webpack.HotModuleReplacementPlugin 用于启用局部模块热重载方便咱们开发,若是要配置代理的话,须要配置 devServer 下的 proxy,具体每一个字段的意思,能够参照官网

关于 source-map 的话,能够理解它为你的源码与打包后代码的一个映射,由于打包后的代码都是通过压缩的,寻找错误调试会很麻烦,因此须要它,这里使用 eval-source-map ,对应的配置 devtool 选项。

const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.common')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const devConfig={
  mode: 'development', 
  devtool: 'eval-source-map',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'public/index.html',
      inject: true
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    host: 'localhost',
    port: 3000,
    historyApiFallback: true,
    overlay: {//当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
      errors: true
    },
    inline: true,
    hot: true,
    // proxy: {
    // '/api/v1': {
    // target: '',
    // ws: true,
    // changeOrigin: true,
    // pathRewrite: {
    // '^/api/v1': '/api/v1'
    // }
    // }
    // }
  },
}

module.exports=merge(baseConfig,devConfig)
复制代码

而后在 package.json 中 scripts 添加咱们启动开发环境的命令,以后就能够启动项目了。

"dev": "webpack-dev-server --config build/webpack.dev.js --mode development --open"
复制代码

生产环境配置

首先在 build 下新建一个 webpack.prod.js,跟开发环境同样,都须要引入公共配置,而后一点点的引入插件。

const merge = require('webpack-merge')
const baseConfig = require('./webpack.common')
const webpack = require('webpack')

const prodConfig = {
  mode: 'production',
  devtool: 'source-map'
}
module.exports = merge(baseConfig, prodConfig)
复制代码

html-webpack-plugin

开头介绍过它,用于自动生成html,并默认将打包生成的js、css引入到html文件中,其中minify 配置项有不少,具体能够参照html-minifier

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

const prodConfig = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
      minify: {
        removeComments: true, // 去掉注释
        collapseWhitespace: true, // 去掉多余空白
        removeAttributeQuotes: true // 去掉一些属性的引号,例如id="moo" => id=moo
      }
    })
  ]
}
复制代码

mini-css-extract-plugin

使用mini-css-extract-plugin来将css从js里分离出来,而且支持chunk css。

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: assetsPath('css/[name].[contenthash].css'),
      chunkFilename: assetsPath('css/[name].[id].[contenthash].css')
    })
  ]
}
复制代码

除此以外还要配置 webpack.commom.js, 把 style-loader 换成这个插件提供的 loader,固然也能够区分一下环境,开发环境仍然使用 style-loader,以 css 文件为例。

{
    test: /\.css$/, // 正则匹配文件路径
    exclude: /node_modules/,
    use: [
      // 
      APP_ENV !== 'dev' ? MiniCssExtractPlugin.loader : 'style-loader',
      {
        loader: 'css-loader', // 解析 @import 和 url() 为 import/require() 方式处理
        options: {
          importLoaders: 1 // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
        }
      },
      'postcss-loader'
    ]
  }
复制代码

clean-webpack-plugin

用于清除本地文件,在进行生产环境打包的时候,若是不清除dist文件夹,那么每次打包都会生成不一样的js文件或者css文件堆积在文件夹中,注意版本带来的使用不一样

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new CleanWebpackPlugin(),
  ]
}
复制代码

optimize-css-assets-webpack-plugin

在webpack打包时优化压缩css代码,主要使用 cssnano 压缩器,这个就不是配置在 plugins 里了,而是 optimization 下的 minimizer

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// ...
const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    minimizer: [
      new OptimizeCssAssetsPlugin({
        cssProcessor: require('cssnano'), // 使用 cssnano 压缩器
        cssProcessorOptions: {
          reduceIdents: false,
          autoprefixer: false,
          safe: true,
          discardComments: {
            removeAll: true
          }
        }
      })
    ]
  }
}
复制代码

terser-webpack-plugin

optimize-css-assets-webpack-plugin 用于压缩 css 代码,而它用来压缩 js 代码,以前用到的是 uglifyjs-webpack-plugin 这一个,可是它好像须要 babel 的支持,并且如今官方推荐用 terser-webpack-plugin, 不过在使用上差很少,并且它不须要安装。

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

// ...
const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    minimizer: [
      new TerserPlugin({
        cache: true,
        // parallel: true,
        terserOptions: {
          compress: {
            warnings: true,
            drop_console: true,
            drop_debugger: true,
            pure_funcs: ['console.log'] // 移除console
          }
        },
        sourceMap: true
      }),
    ]
  }
}
复制代码

webpack.RuntimeChunk

它能够将包含chunks 映射关系的 list单独从 app.js里提取出来,由于每个 chunk 的 id 基本都是基于内容 hash 出来的,因此你每次改动都会影响它,若是不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。在 webpack4 中,无需手动引入插件,配置 runtimeChunk 便可。

const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    {
      runtimeChunk: true;
    }
  }
}
复制代码

打包生成的 runtime.js很是的小,gzip 以后通常只有几 kb,但这个文件又常常会改变,咱们每次都须要从新请求它,它的 http 耗时远大于它的执行时间了,因此建议不要将它单独拆包,有关优化就是将他将它内联到咱们的 index.html 之中。

这里使用了 script-ext-html-webpack-plugin。

const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

// 注意必定要在HtmlWebpackPlugin以后引用
// inline 的name 和你 runtimeChunk 的 name保持一致
new ScriptExtHtmlWebpackPlugin({
  //`runtime` must same as runtimeChunk name. default is `runtime`
  inline: /runtime\..*\.js$/
});
复制代码

webpack.splitChunks

这个配置能让咱们以必定规则抽离想要的包,webpack4 有一套默认的代码分包策略。

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩以前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

关于按需加载跟页面初始加载就对应到 webpack.splitChunks.chunks 它表示将选择哪些块进行优化,async 表示只优化动态导入的包,而 initial 表示初始加载时导入的包,还有一个值 all 表示都会优化,默认是 async,也就是说若是你动态导入了一个包,压缩前大于30kb,而且你在代码中有超过5个地方引用了它,那么 webpack 就会将它单独打包出来。

一般咱们须要将 node_modules 下的比较大的基础类库包抽出来,好比 vuex、vue之类的,或者像比较大的UI 组件库,好比 antd、element-ui 之类的也抽出来,以及本身写的可能会在多个页面间用到屡次的组件。下面给一个我这里的配置,注意:拆包的时候不要过度的追求颗粒化,资源的加载策略并没什么彻底的方案,都须要结合本身的项目找到最合适的拆包策略

const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    splitChunks: {
      chunks: 'async', // 提取的 chunk 类型,all: 全部,async: 异步,initial: 初始
      // minSize: 30000, // 默认值,新 chunk 产生的最小限制 整数类型(以字节为单位)
      // maxSize: 0, // 默认值,新 chunk 产生的最大限制,0为无限 整数类型(以字节为单位)
      // minChunks: 1, // 默认值,新 chunk 被引用的最少次数
      // maxAsyncRequests: 5, // 默认值,按需加载的 chunk,最大数量
      // maxInitialRequests: 3, // 默认值,初始加载的 chunk,最大数量
      // name: true, // 默认值,控制 chunk 的命名
      cacheGroups: { // 配置缓存组
        vendor: {
          name: 'vendor',
          chunks: 'initial',
          priority: 10, // 优先级
          reuseExistingChunk: false, // 容许复用已经存在的代码块
          test: /node_modules\/(.*)\.js/, // 只打包初始时依赖的第三方
        },
        common: {
          name: 'common',
          chunks: 'initial',
          // test: resolve("src/components"), // 可自定义拓展你的规则
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
}
复制代码

webpack.DllPlugin 与 webpack.DllReferencePlugin

像 React 相关基础运行环境,将这些基础模块打到一个包里,只要这些包的包的版本没升级,之后每次打包就不须要再编译这些模块,提升打包的速率,这里咱们就能够用到 webpack.DllPlugin,而后使用 webpack.DllReferencePlugin 将这个 dll 包关联到当前的编译中去。

在 build 文件夹下新建一个 webpack.dll.js 文件,并写入下面的配置

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  mode:'production',
  entry: {
    // 还有redux 之类的也能够放进来
    vendor: ['react', 'react-dom', 'react-router-dom']
  },
  output: {
    filename: '[name].dll.[hash:8].js',
    path: path.join(__dirname, '../dll'),
    // 连接库输出方式 默认'var'形式赋给变量
    libraryTarget: 'var',
    // 全局变量名称 导出库将被以var的形式赋给这个全局变量 经过这个变量获取到里面模块
    library: '_dll_[name]_[hash:8]'
  },
  plugins: [
    // 每次运行时清空以前的 dll 文件
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [path.join(__dirname, '../dll/**/*')]
    }),
    new webpack.DllPlugin({
      // path 指定manifest文件的输出路径
      path: path.join(__dirname, '../dll/[name].manifest.json'),
      // 和library 一致,输出的manifest.json中的name值
      name: '_dll_[name]_[hash:8]'
    })
  ]
}
复制代码

下面修改 webpack.prod.js 使用DllReferencePlugin告诉 Webpack 使用了哪些动态连接库,而后并使用下面介绍的 add-asset-html-webpack-plugin 将其放入资源列表 html webpack插件注入到生成的 html 中。

其中 vendor.manifest.json 是由 DllPlugin 生成出,用于描述动态连接库文件中包含哪些模块。

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    // 告诉 Webpack 使用了哪些动态连接库
    new webpack.DllReferencePlugin({
      manifest: path.join(__dirname, `../dll/vendor.manifest.json`)
    })
  ]
}
复制代码

以后在 package.json 中scripts再加一个命令

"scripts": {
    "dll": "webpack --config build/webpack.config.dll.js",
  }
复制代码

而后运行它,就能够发现根目录下dll生成了两个文件 vendor.dll.xxxxxxxx.js,vendor.manifest.json

add-asset-html-webpack-plugin

咱们使用它来将给定的静态资源css或者js引入到html-webpack-plugin生成的html文件中。

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new AddAssetHtmlPlugin({
      filepath: resolve(`${DLL_PATH}/**/*.js`),
      includeSourcemap: false
    }),
  ]
}
复制代码

webpack-bundle-analyzer

若是你想看你webpack打包以后输出文件的大小占比,可使用这个插件,在webpack.prod.js 中加入以下配置,若是你想控制这个插件是否引入,可使用一个变量:

if (config.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  prodConfig.plugins.push(new BundleAnalyzerPlugin())
}
复制代码

这样在打包结束后,会自动打开一个浏览器窗口,并展现输出文件的大小占比。

性能提示

若是想要在打包或者开发过程当中展现一些性能提示,能够在 webpack.common.js 中加入以下配置。

module.exports={
   // ...
   performance: { // 性能提示,能够提示过大文件
    hints: "warning", // 性能提示开关 false | "error" | "warning"
    maxAssetSize: 100000, // 生成的文件最大限制 整数类型(以字节为单位)
    maxEntrypointSize: 100000, // 引入的文件最大限制 整数类型(以字节为单位)
    assetFilter: function(assetFilename) {
        // 提供资源文件名的断言函数
        return (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetFilename))
    }
  } 
}
复制代码

最后

到这里生产开发环境的配置基本上就结束了,若是有漏掉的或者配置不对的地方,但愿大佬指出。

最后附上地址 项目地址,若是有不对的地方但愿各位指出,感谢。

参考的文章:

相关文章
相关标签/搜索