从零搭建项目(7) --- 前端: 打包与环境变量设置

我的博客地址

正式地址
测试地址
前端源码
后端源码

文章目录

  1. 项目及其技术栈介绍
  2. 前端: 项目初始化
  3. 前端: 使用Sass和Antd
  4. 前端: 开发体验优化
  5. 前端: 搭建路由和状态管理
  6. 前端: 支持Axios
  7. 前端: 打包与环境变量设置
  8. 前端: 团队代码规范
  9. 后端: 项目初始化和使用Koa相关
  10. 后端: 使用TypeORM和MySQL
  11. 部署: 使用nginx部署前端项目
  12. 部署: 后端部署
  13. 部署: 使用jenkins自动化部署

前言

在前端开发完成后,都需要将编写的代码编译打包成静态文件,之后才会部署到服务器上,而WebPack就是这样一个打包工具,它所有的loader、plugin都是围绕这一个功能进行的扩展,这篇文章就在之前搭建的基础上来介绍一下WebPack的打包配置吧,所用到的知识点如下:

  1. 打包路径和命令添加
  2. filenamechunkFilename: hashchunkhashcontenthash
  3. publicPath
  4. 单独打包CSS
  5. optimization优化打包
  6. 环境变量设置区分生产开发环境

打包路径和命令添加

  • 添加打包路径
    其实在开始介绍前端搭建的第一篇文章中我们就已经将打包的入口和出口都在webpack.config.js中制定好了:

    image.png

    这段配置意思是让WebPack通过src/index.tsx作为入口,递归地建立模块依赖关系,然后打包输出模块,模块输出后存放在根目录的dist文件夹中

  • 添加打包命令
    去到package.json加上如下命令:

    image.png

  • 测试打包
    我们运行npm run build之后可以看到包已经打好了:

    image.png

filename和chunkFilename

webpackoutput配置中,filename是打包文件名,也就是说我们列在entry中的文件打包出来的名字,在本例中就是app:

image.png

chunkFilename则是未列在entry中的模块文件,下面划红线的都属于chunk file:
image.png

filenamechunkFilename则是对这两种文件命名的设定,这个命名很重要吗?
是的,因为浏览器存在缓存问题,如果按照目前配置,每一次代码变化的情况下打包出来的文件名不变,浏览器优先加载缓存中的文件,然后导致页面没有展示最新的页面情况。

  • 要解决这个问题我们先把chunkFilename设置好:

    image.png

  • 然后可以通过给文件名设置hash值的方式保证每次打包的文件名都是不同的,filenamechunkFilename都支持该设置:

    image.png

  • 另外这个hash值的设置有三种: 分别是hashchunkhashcontenthash

    1. hash: hash和整个项目的配置有关,只要项目中有代码改变,那么所有打包出来的hash值都会变,并且所有文件共用一个hash值
    2. chunkhash: chunkhashhash不同点在于,它根据入口文件进行依赖文件解析,然后构建对应的hash值,也就是每个打包出来的文件hash值都是不一样的,每次修改代码时候,他会根据依赖关系自动修改相关模块的hash值,但是打包出来对应的js和css文件的hash会相同。
    3. contenthash: 在打包代码的时候,一般会将CSS文件分离出来,然后我们通常会在组件中引入CSS文件,这时候如果使用的是chunkhash,在只修改组件js代码的情况下因为对应的css文件的hash值相同,打包出来的css文件的hash值也会跟着变,这时候就可以使用contenthash了,他会针对每个文件的内容来计算hash值。

在本例中,我们就使用chunkhash即可,CSS的contenthash可以在后面对css文件进行分离的时候再配置。

publicPath

publicPath也是ouput中的一个属性,用于处理打包后的引入资源路径,比如上面我没有添加publicPath的时候,打包出来的index.html引入资源的路径:

image.png

现在我把publicPath设置为/,再来看看打包出来的index.html的引入资源路径就成了绝对路径了:
image.png

image.png

那么这个配置有什么用处呢?

  • 用处
    在我们对前端项目打包完毕之后,js和css等较大的静态资源都会被上传到cdn上面,而index.html则会留在我们自己的服务器上,用户访问index.html的时候,index.html加载的css和js路径就应该是cdn域名上的资源,也就是说假设我的cdn域名为https://cdn.xxx.com/,那么这串东西就应在放到publicPath中让他自己在资源引入的前面加上,比如下面的例子:
    image.png

    image.png

单独打包CSS

在前面的打包配置中,我们虽然可以打包成功,但是这时候的css是被嵌入到js文件中的:


image.png

这对于模块化来说不大好,而且还会导致js文件变得更大从而降低网页加载速度,所以需要将css额外抽离出来。

  • 使用mini-css-extract-plugin抽离css
    在webpack4.0后,推荐使用mini-css-extract-plugin来对css文件进行分离,首先我们需要安装mini-css-extract-plugin包,这个包里面包含一个loader和一个plugin,后续需要分别进行配置
    npm i -D mini-css-extract-plugin

  • 配置loader
    接下来我们去到build/rules/styleRules.js文件中引入他:

    image.png

    在这里需要注意一点:
    sass-loader是将sass文件编译为css,而css-loader是将css转为CommonJS模块,style-loader是将对应的JS生成为style 节点,那么如果我们要分离css文件的话,应该是在css-loader之后进行,所以下面我们把之前style-loader的地方全都替换成MiniCssExtractPlugin:
    image.png

  • 配置plugin
    然后去到build/plugins.js文件中,引入并使用mini-css-extract-plugin并使用,配置主要是filenamechunkFilename:

    image.png

    注意这里需要用contenthash而不是chunkhash,否则打包出来的和引用该css的js文件的hash值是一样的,而且改css还会导致打包后的js文件的hash值产生变化。

  • 打包结果
    可以看到css文件已经被抽离了出来,并且js和css文件的hash值是不一样的:


    image.png

使用optimization优化打包

在上面的打包配置中,因为之前做了路由组件的代码分割,所以组件PageAPageB会被单独打包成js文件,再来看看打包的情况表,会发现page-a的公用模块(引入的库)体积高达800多k,这对于网页加载资源而言是非常大的:

image.png

我们可以通过配置WebPack的optimization来对代码进一步分割,使得打出来的包更小。

  • 首先我们去到build文件夹下新建文件optimization.js,新建并导出一个对象:

    image.png

    然后配置runtimeChunk:
    image.png

    runtime是指webpack运行环境(模块解析和加载)和模块信息清单,而runtimeChunk就是询问该清部分清单代码是否单独打包出来,我们这里将其单独打包出来并命名为manifest

  • 然后我们通过配置splitChunkcacheGroups将一些公共模块分离出来打包:

    image.png

    这里面的priority是优先级的意思,数字越大优先级越高,antd依赖moment,所以moment优先级比antd高,然后commons是剩余模块统一打包不做分割了。

  • 接下来我们把optimization导入到webpack中去使用:

    image.png

    然后查看一下打包结果:
    image.png

    可以清楚的看到之前vendor~page-a文件从发800多k变成了500多k,另外还多出来一个vendor文件有270多k,说明是可以显著减少打包出来的文件的大小的。
    如果还需要继续细分,可以继续在splitChunks.cacheGroups配置中将公用模块继续细分并添加进去,使得打包的代码越来越小

  • 压缩js优化和压缩css代码并优化

    1. 使用terser-webpack-plugin优化js压缩过程:
      terser-webpack-plugin是一个js代码优化插件,他可以使用多线程和缓存更快的压缩js代码,优化打包体验,并且使用非常简单。
      为什么使用这个插件呢?因为webpack默认使用的webpack.optimize.UglifyJsPlugin插件是不支持es6语法的,当然你可以先用babel转一下再用UglifyJsPlugin也没所谓。
      首先我们安装npm i -D terser-webpack-plugin
      然后在build/optimization.js文件中配置minimizer,导入terser-webpack-plugin并使用即可:

      image.png

    2. 压缩css代码并优化压缩过程
      在之前的打包中,我们的js代码虽然已经经过了压缩,但是css代码还没有压缩:

      image.png

      这时候我们需要使用optimize-css-assets-webpack-plugin来做这件事。
      首先安装它npm i -D optimize-css-assets-webpack-plugin
      然后继续在minimizer中进行配置:
      image.png

最后我们重新打包来看看结果:


image.png

可以看到css文件也被压缩了。

环境变量设置区分生产开发环境

在webpack4.0之后,打包的时候会出现这个警告:

image.png

这是因为在4.0后webpack受到parcel的竞争,而parcel就是号称0配置的打包器,所以webpack也内置了一套默认的打包配置,但是开发环境和生产环境的配置是不一样的,所以需要通过配置webpack.mode属性来告诉webpack处于什么环境,另外开发环境和生产环境有些不一样,例如生产环境一般不需要sourcemap功能,之前打包出来很多.map文件就是因为开启了sourcemap所致,所以我们也需要通过环境变量来对此进行区分。

  • 使用cross-env设置环境变量
    cross-env是一个专门用于设置webpack运行或者打包时候的进程环境变量的工具,注意是进程环境变量而不是全局变量,这中变量在webpack打包结束后就没有了,所以不能写入到业务代码中(当然也是可以写入的,不过需要另外配置)。

我们首先安装它npm i -D cross-env
然后去到package.json文件中,在script定义的命令中插入环境变量, 使用windows的同学需要注意,写法可能有点不一样,具体谷歌即可:

image.png

这一步的目的就是将NODE_ENV这个变量插入到进程中,开发和生产分别是developmentproduction

然后我们去到webpack.config.js中,添加mode属性的设置,通过process.env.NODE_ENV即可获取之前设置的环境变量:

image.png

这样就消除了上面所说的webpack的警告了。

  • 通过环境变量分别配置开发生产webpack配置
    既然已经可以区分生产和开发环境,那么webpack中的有些配置也可以进行区分了:


    image.png
  • 注入环境变量到代码中
    按照前面的说法,通过cross-env注入的变量只能存在于webpack打包或者编译的进程中,那么有时候我们需要在代码运行的过程中获取到这个环境变量怎么办呢?
    例如上一章我们配置Axios的时候的baseURL就需要区分生产和开发环境:

    image.png

    当然也是有解决方案的,我们可以使用WebPack自带的插件DefinePlugin做到这点。

首先我们去到build/plugins.js文件中,在里面引入这个插件,然后进行变量配置即可:

image.png

之后我们到PageA组件中,使用process.env.NODE_ENV变量查看一下效果:

image.png

可以看到变量已经被注入到了代码中,而不是只存在于webpack打包和编译的进程中了:
image.png

另外还需要记得把baseURL换成生产环境的url,在本例中,生产环境的请求url是https://test.oxcblog.club/api:

image.png

最后

经过上面的这么多步骤,我们的webpack配置也基本算是告一段落了,下一篇文章将会介绍使用团队代码规范相关的插件: commitlint、eslint、stylelint等。

推荐阅读更多精彩内容

  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 3,041评论 2 30
  • /*生成环境配置文件:不需要一些dev-tools,dev-server和jshint校验等。和开发有关的东西删掉...
    David_Sam阅读 291评论 0 1
  • 前端将大型项目分成一个个单独的模块,一般封装好的每个模块都会实现一个目的明确的完成的功能。如何处理这些模块以及模块...
    pixels阅读 2,109评论 1 14
  • 更新:Webpack4已经发布,本篇是基于Webpack3的,请注意。更正:1.package.json中使用了应...
    HermitCarb阅读 436评论 0 5
  • 最近感觉自己越来越像一个API调用程序员,很多基础的原理以及项目构建都没实际操作过,所以这里动手自己去搭建了一个v...
    Aoyi_G阅读 247评论 0 1