前端工程化与Webpack

目标:
了解工程化的前端开发方式
知道Webpack在实际开发中所起到的作用

我们只需要了解webpack的工作原理,并不要求我们掌握如何配置,因为工作中我们会使用一些工具如Vue-CLI脚手架,它们已经帮我们配置好了

webpack 的基本使用:安装、webpack.config.js、修改打包入口
webpack的两个插件(常用的plugin的基本使用):
webpack-dev-server – (每次修改后自动进行项目的打包和构建)
html-webpack-plugin – (把src里面的首页在内存中复制一份,放到根目录中)
常用的loader 的基本使用:协助webpack打包处理特定的文件模块。
Source Map 的作用:精准定位到错误行并显示对应的源码,方便开发者调试源码中的错误

前端工程化相关概念

前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。
好处:前端开发自成体系,有一套标准的开发方案和流程。

实际的前端开发,是四个现代化:

  • 模块化 (js的模块化、css的模块化、资源的模块化) ———— js的复用
  • 组件化 (复用现有的UI结构、样式、行为) ———— UI结构的复用
  • 规范化 (目录结构的划分、编码规范化、接口规范化、文档规范化、Git分支管理)
  • 自动化 (自动化构建、自动部署、自动化测试)

前端工程化解决方案

webpack 常见用法

  1. 什么是webpack?
    概念:webPack是前端项目工程化的具体解决方案
    主要功能: 它提供了友好的前端模块化开发支持,以及代码压缩混淆(干掉注释和空格)、处理浏览器端JavaScript的兼容性性能优化等强大的功能。
    好处:让程序员把工作的重心放到具体功能的实现上,提高了前端开发效率和项目的可维护性。
    注意:目前Vue,React等前端项目,基本上都是基于webpack进行工程化开发的。

  2. 创建列表隔行变色项目

    1. 新建项目空白目录,并运行npm init -y命令,初始化包管理配置文件 package.json
    2. 新建src源代码目录
    3. 新建src -> index.html首页和src -> index.js 脚本文件
    4. 初始化首页基本的结构
    5. 运行npm install jquery -S命令,安装jQuery(-S明确告诉npm,包要记录到dependencies下,开发上线都会用到)
    6. 通过ES6模块化的方式导入jQuery,实现列表隔行变色效果
  3. 在项目中安装webpack
    运行如下命令,安装webpack相关的两个包(-D告诉npm记录到devDependencies下,只在开发阶段会用到)
    npm install webpack@5.42.1 webpack-cli@4.7.2 -D

  4. 在项目中配置webpack

    1. 在项目根目录中,创建名为webpack.config.js的webpack配置文件,并初始化如下的基本配置:

      1
      2
      3
      4
      5
      module.exports = {
      // mode 用来指定构建模式。可选值有 development和 production
      // 开发阶段选develement,上线改成production
      mode: 'development'
      }
    2. 在package.json的 scripts节点下,新增dev脚本如下:
      这个dev是可以随意命名的,后面的webpack是必须写webpack
      scripts节点下有个默认的test脚本,删掉就行

      1
      2
      3
      4
      "scripts": {
      // script 节点下的脚本,可以通过 npm run 执行。例如 npm run dev
      "dev": "webpack"
      }
    3. 在终端中运行npm run dev命令,启动 webpack进行项目的打包构建
      运行成功后,项目会多一个dist文件夹,里面的main.js就是webpack根据index.js帮我们自动生成的,做一下兼容性处理
      处理完以后,index.js还有兼容性问题,而main.js没有兼容性问题
      所以网页应该引入的是main.js文件
      .
      由nodejs升级引起的构建错误:0308010C:digital envelope routines::unsupported
      https://juejin.cn/post/7202639428132044858
      报错原因可以看这篇文章
      解决设置(似乎还没有一个100%完全的解决方法,都会存在一些问题):
      "dev": "set NODE_OPTIONS=--openssl-legacy-provider && webpack"
      .
      这个main.js是webpack帮我们把jquery.js和index.js文件进行了合并,但是并没有压缩,main.js中还有注释和代码缩进,如果要压缩,需要把webpack.config.js的mode设置为production,然后重新运行,整个文件的体积就被压缩的更小了,但是打包的时间更长了
      .
      所以开发的时候,mode都设置成develement,只有上线的时候才会设置为production

    4. webpack的默认约定 ———— 如何打包
      在webpack 4.x和5.的版本中,有如下的默认约定:

      1. 默认的打包入口文件为src -> index.js
      2. 默认的输出文件路径为dist -> main.js

      注意:可以在webpack.config.js中修改打包的默认约定
      如果找不到要处理的文件,就会报错

    5. 自定义打包的入口和出口
      在webpack.config.js 配置文件中,通过entry节点指定打包的入口。通过output节点指定打包的出口。示例代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const path = require( 'path') //导入 node.js中专门操作路径的模块
      module.exports = {
      //打包入口文件的路径
      entry: path.join(__dirname,'./src/index.js'),
      output: {
      //输出文件的存放路径
      path: path.join(__dirname,'./dist'),
      //输出文件的名称
      filename: 'bundle.js'
      }
      }

这个时候,我们会发现,每次我们更改index.js的代码之后,都需要再次执行npm run dev更新生成的文件才行,这样非常麻烦,所以我们需要用到webpack的插件

webpack 插件

最常用的webpack插件有两个:

  1. webpack-dev-server
    • 类似于node.js的nodemon工具
    • 每当修改了源代码,webpack都会自动进行项目的打包和构建
  2. html-webpack-plugin
    • webpack 中的HTML插件(类似于一个模板引擎插件)
    • 可以通过此插件自定制index.html页面的内容

webpack-dev-server

  1. 安装webpack-dev-server
    npm i webpack-dev-server@3.11.2 -D
    .
    在配置好之后,npm run dev 运行报错
    Unable to load ‘@webpack-cli/serve’ command
    TypeError: options.forEach is not a function
    .
    下载npm install webpack@5.42.1 webpack-cli@4.10.0 -D

  2. 配置webpack-dev-server

    1. 修改package.json -> scripts中的dev命令如下:
      1
      2
      3
      4
      "scripts": {
      // script节点下的脚本,可以通过npm run 执行
      "dev" : "webpack serve",
      }
    2. 再次运行npm run dev命令,重新进行项目的打包
      在浏览器中访问 http://localhost:8080 地址,查看自动打包效果

    注意:webpack-dev-server 会启动一个实时打包的http服务器

注意,webpack-dev-server启动后,构建后的文件是在内存中的,不会实际生成具体的文件,所以我们打开的页面效果不会发生变化

我们执行npm run dev 命令,它执行的其实是webpack serve命令
这个命令执行成功后,第一行返回的是,
i 「wds」: Project is running at http://localhost:8080/
是整个项目运行在8080端口
所以,要看更新后的效果,要打开这个网址来查看,不能和以前一样的方法查看,因为之前查看的是file协议,不是当前的http协议

我们打开 http://localhost:8080/ 网址,进入src,浏览器就会自动打开这个目录下的index文件,但是还是页面没有更改,我们再看第二行
i 「wds」: webpack output is served from /
告诉我们,输出在根目录,但是根目录并看不到bundle.js,但是其实它是有的,我们打开http://localhost:8080/bundle.js 可以访问到该文件

webpack-dev-server这个插件,它把生成的bundle没有放到实际的物理磁盘上,而是放到了内存里面
这是因为,如果频繁的修改文件,ctrl s,如果是把文件放到物理磁盘上,就会频繁的读写磁盘,对磁盘寿命性能都会有很大影响,所以要访问最新的bundle要访问内存里的bundle,所以应该在页面加载和引用内存里的bundle.js

1
2
3
<script src="http://localhost:8080/bundle.js"></script>
// 也可以直接这样引用,看不见,但是可以引用的
<script src="/bundle.js"></script>

但是由于我的vscode默认的使用5500端口,所以第二种引用会引用到5500端口,但是5500端口并不存在bundle.js,所以会报错,只能使用第一种引用

html-webpack-plugin

有一点不方便的是,打开 http://localhost:8080/ 之后,我们还要点击一次src进入目录中,才能打开首页
我们希望一进入8080就能看到首页
html-webpack-plugin,这个插件可以把src里面的首页复制一份,放到根目录中
安装插件:npm i html-webpack-plugin@5.3.2 -D

  • 配置 – 在webpack.config.js中配置
    把new出来的插件htmlPlugin 挂载/添加到plugins插件的数组里面,这样webpack在运行的时候就会调用这个插件的实例,把src(template)里的页面复制一份放到根目录(filename)中
    不过也是复制出来的一个内存里的页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 1. 导入HTML插件,得到一个构造函数
    const HtmlPlugin = require('html-webpack-plugin')
    // 2.创建HTML插件的实例对象
    const htmlPlugin = new HtmlPlugin({
    template: './src/index.html',//指定原文件的存放路径
    filename: './index.html',//指定生成的文件的存放路径
    })

    module.exports = {
    mode: 'development' ,
    // 3.通过 plugins节点,使 htmlPlugin插件生效
    plugins: [htmlPlugin],
    }
  • 另一个作用
    html-webpack-plugin插件除了帮我们复制页面,还会在复制出来的页面,自动注入一个脚本(js引用),去引用内存里的bundle.js
    .
    我们npm run dev运行之后,如果把html页面的js引入注释掉,内存中页面样式是还在的,并没有消失。
    我们查看源码,可以发现它是有js引用的,而且是没有加./的同级目录引用,因为内存中的html和hundle.js在同一级目录

配置完webpack之后,我们的开发体验会变的很好,但是配置过程又非常繁琐。
我不想自己配置,又想体验配置后的好处 ———— Vue-cli
这个工具已经帮我们配置好了webpack

devServer 节点

还有一个不爽的点(程序员的很多不爽XDD),每次重新运行项目,都要手动打开8080页面,我们懒就要懒到底
在npm run dev运行成功之后,自动打开浏览器展示网页

  • 配置
    在webpack.config.js配置文件中,可以通过devServer节点对webpack-dev-server插件进行更多的配置,示例代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    devServer: {
    // 初次打包完成后,自动打开浏览器
    open: true,
    // 实时打包所使用的主机地址
    host: '127.0.0.1',
    //实时打包所使用的端口号
    port: 80,
    }

webpack中的loader(加载器)

  1. loader概述
    在实际开发过程中,webpack默认只能打包处理以.js 后缀名结尾的模块。其他非.js后缀名结尾的模块,webpack默认处理不了,需要调用loader加载器才可以正常打包,否则会报错!

    loader加载器的作用:协助webpack打包处理特定的文件模块。比如:

    • css-loader可以打包处理.css相关的文件
    • less-loader可以打包处理.less相关的文件
    • babel-loader可以打包处理webpack无法处理的高级JS语法

    在webpack里面,一切都可以进行导入,一切皆模块,不建议程序员手动去引入样式和脚本(link,script)

    在js中也可以导入样式(ES6语法)import './css/index.css'

  2. 打包处理 css 文件

    1. 运行npm i style-loader@3.0.0 css-loader@5.2.6 -D命令,安装处理css文件的loader
    2. 在webpack.config.js 的 module -> rules 数组中,添加loader规则如下:
      1
      2
      3
      4
      5
      6
      module: { 
      //所有第三方文件模块的匹配规则
      rules: [ //文件后缀名的匹配规则
      { test: /\.css$/, use: ['style-loader', 'css-loader']}
      ]
      }
      其中,test表示匹配的文件类型, use表示对应要调用的loader
      注意:
    • use数组中指定的loader 顺序是固定的
    • 多个loader的调用顺序是: 从后往前调用
      loader在调用的时候,是从后往前调的 loader调用过程
  3. 打包处理 less文件

    1. 运行npm i less-loader@10.0.1 less@4.1.1 -D命令
    2. 在webpack.config.js 的module -> rules数组中,添加loader规则如下:
      1
      2
      3
      4
      5
      module: { //所有第三方文件模块的匹配规则
      rules: [ //文件后缀名的匹配规则
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      ]
      }
      这个less是内部依赖项,就是less-loader内部依赖于less,但是不需要手动的去声明less
  4. 打包处理样式表中与url路径相关的文件

    1. 运行npm i url-loader@4.1.1 file-loader@6.2.0 -D命令
    2. 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
      1
      2
      3
      4
      5
      6
      module: { //所有第三方文件模块的匹配规则
      rules:[//文件后缀名的匹配规则
      //如果需要调用的loader只有一个(use),则只传递一个字符串也行,如果有多个loader,则必须指定数组
      { test: /\.jpg|png|gif$/,use: 'url-loader?limit=22229'},
      ]
      }
      其中?之后的是 loader的参数项:
    • limit用来指定图片的大小,单位是字节(byte)
    • 只有≤limit 大小的图片,才会被转为base64格式的图片
  5. 打包处理js文件中的高级语法
    webpack只能打包处理一部分高级的JavaScript语法。对于那些webpack无法处理的高级js语法,需要借助于babel-loader进行打包处理。例如webpack无法处理下面的JavaScript代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 1.定义了名为info的装饰器
    function info(target) {
    //2.为目标添加静态属性info
    target.info = 'Person info'
    }
    // 3.为Person类应用info装饰器
    @info
    class Person {}

    // 4.打印 Person 的静态属性info
    console.log(Person.info)

5.1 运行如下的命令安装对应的依赖包:

1
npm i babel-loader@8.2.2 @babel/core@7.14.6 @babel/plugin-proposal-decorators@7.14.5 -D

在webpack.config.js的 module -> rules数组中,添加loader规则如下:

1
2
//注意:必须使用 exclude 指定排除项;因为 node_modules 目录下的第三方包不需要被打包
{ test: /\.js$/, use: 'babel-loader' , exclude: /node_modules/ }

exclude排除了node_modules文件夹,因为只需要处理我们自己写的代码就可以了,第三方的包没有兼容性问题,本身已经解决了,不需要处理(如果不排除也会对其进行处理影响性能)

5.2 配置babel-loader
打包处理js文件中的高级语法,比处理其他文件要多这么一步
在项目根目录下,创建名为 babel.config.js 的配置文件,定义Babel的配置项如下:

1
2
3
4
5
//这些配置从官方文档找到对应项直接粘过来就行了
module.exports = {
//声明babel 可用的插件
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]]
}

详情请参考Babel的官网https://babeljs.io/docs/en/babel-plugin-proposal-decorators

loader 调用过程

  1. webpack默认只能打包处理.js结尾的文件,处理不了其它后缀的文件
  2. 由于代码中包含了 index.css 这个文件,因此 webpack默认处理不了
  3. 当webpack 发现某个文件处理不了的时候,会查找 webpack.config.js 这个配置文件,看 module.rules 数组中,是否配置了对应的 loader 加载器。
  4. webpack 把 index.css 这个文件,先转交给最后一个 loader 进行处理(先转交给css-loader) .
  5. 当 css-loader 处理完毕之后,会把处理的结果,转交给下一个 loader (转交给style-loader)
  6. 当 style-loader 处理完毕之后,发现没有下一个 loader 了,于是就把处理的结果,转交给了 webpack
  7. webpack 把 style-loader 处理的结果,合并到/dist/bundle.js中,最终生成打包好的文件。
    loader调用过程

base64图片的优缺点

浏览器在拿到HTMl标签以后,需要再发送请求,拿到图片以后才能把图片渲染出来

但是使用用base64图片,在拿到img标签以后,就顺手的把图片对应的字符串也给请求下来了,可以防止发起一些不必要的网络请求

如果页面有许多小图标,就要发送很多次请求,对性能来讲非常不好,就可以把图片都转成base64格式的字符串

缺点:
图片转成base64,体积会增大一点点,所以大图片就不适合

ES6 import导入图片得到的是一个base64字符串(可以打印看一下)
import logo from './images/logo.png'

webpack处理样式的过程

ES6在js文件导入样式import './css/index.css'
这种语法代表,只需要加载这个文件,把css样式给加载过来,不需要接收(接收的话,打印出来是undefined)

webpack加载和处理js文件,处理的结果会生成一个新的js文件(如bundle.js),而且会在页面引用过去,在这个bundle.js里面,会包含我们import引入的样式,在这个bundle.js里面,css样式会被转成js的方式去运行

打包发布

我们如果要上线,应该拿到这些文件发给后端,让后端去部署上线
发布上线就是:
前端把项目生成一份最终的结果,有页面,配套的css js 图片等等
所有资源都在生成的文件夹里面,把所有内容打包发给后端
后端拿到结果去部署上线

现在我们要解决的问题是:页面和bundle.js放到内存里面了,需要把它生成到实际的物理磁盘上。

配置webpack

在package.json文件的scripts节点下,新增build命令如下:

1
2
3
4
"scripts": {
“dev" : "webpack serve",//开发环境中,运行dev命令
"build": "webpack --mode production" //项目发布时,运行 build命令
}

--mode是一个参数项,用来指定webpack的运行模式。production 代表生产环境,会对打包生成的文件进行代码压缩性能优化
注意:通过--mode指定的参数项,会覆盖webpack.config.js 中的--mode选项。

这里的dev命令是webpack serve,就是我们装的插件,是把文件生成到内存中,可以监听文件的更改自动执行npm run dev
而这里build就是要执行webpack命令把文件生成到磁盘上,这里注意要改成
set NODE_OPTIONS=--openssl-legacy-provider && webpack --mode production

优化图片和js文件存放路径

  1. js文件
    在webpack.js的output对象属性中,把filename前面加上个文件夹
    filename:'js/bundle.js'
  2. 图片
    修改webpack.config.js 中的url-loader配置项,新增outputPath选项即可指定图片文件的输出路径:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    test: /.jpg|png|gif$/ ,
    use: {
    loader: 'url-loader',
    options: {
    limit: 22228,
    //明确指定把打包生成的图片文件,存储到dist目录下的 image 文件夹中
    outputPath: 'image',
    },
    },
    }
    或者直接用?xx=xx&xx=xx的方式在loader后面携带参数
    1
    { test: /\.jpg|png|gif$/, use: 'url-loader?limit=22229&outputPath=images' }

配置和使用clean-webpack-plugin插件自动删除dist目录

webpack5已经默认封装(内置)了该插件,直接在配置文件里output设置属性clean:true即可


为了防止旧文件干扰我们的事件,在每次重新发布的时候会把生成的打包好的dist文件夹删掉。
为了在每次打包发布时自动清理掉dist目录中的旧文件,可以安装并配置dlean-webpack-plugin插件:

1
2
3
4
5
6
7
// 1.安装清理dist目录的 webpack 插件
npm install clean-webpack-plugin@3.0.0 -D
// 2.按需导入插件、得到插件的构造函数之后,创建插件的实例对象
const { CleanwebpackPlugin } = require('clean-webpack-plugin')
const cleanPlugin = new CleanwebpackPlugin()
// 3.把创建的 cleanPlugin 插件实例对象,挂载到plugins节点中
plugins: [htmlPlugin,cleanPlugin],//挂载插件
  • 检验是否是删除之后重新生产的方法
    可以在dist文件夹下新建如1.txt文件,重新打包,如果文件还在,说明是没有删除直接打包的,如果不再了,那就是删除之后重新打包。

企业级项目的打包发布

企业级的项目在进行打包发布时,远比刚才的方式要复杂的多,主要的发布流程如下:

  • 生成打包报告,根据报告分析具体的优化方案
  • Tree-Shaking
  • 为第三方库启用CDN加载
  • 配置组件的按需加载
  • 开启路由懒加载
  • 自定制首页内容

source Map – 开发阶段检错

告诉你文件的报错位置,用于调试bug
开发阶段 npm run dev 检错的
在发布项目的时候,处于安全性考虑,建议关闭SourceMap。防止一些人通过报错位置来拿到我们的源代码(通过修改页面代码来主动让页面某功能报错来拿到该功能位置的源码这样吗?)
新版本开发环境建议使用cheap-module-source-map?

  1. 什么是 source Map
    Source Map就是一个信息文件,里面储存着位置信息。也就是说,Source Map文件中存储着压缩混淆后的代码,所对应的转换前的位置
    有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。

即是:页面报错,控制台会显示错误以及错误文件及其报错位置,如果没有配置这个,它显示的就是打包压缩后的代码的位置

  1. webpack 开发环境下的 Source Map
    在开发环境下,webpack默认启用了Source Map功能。当程序运行出错时,可以直接在控制台提示错误行的位置,并定位到具体的源代码。
    但是,开发环境下默认生成的Source Map,记录的是生成后的代码的位置。会导致运行时报错的行数与源代码的行数不一致的问题。

  2. 解决默认的Source Map的问题
    开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数保持一致:

    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    mode: 'development',
    // eval-source-map 仅限在"开发模式"下使用,不建议在"生产模式"下使用。
    //此选项生成的 Source Map 能够保证"运行时报错的行数"与"源代码的行数"保持一致
    // 在开发调试阶段,建议大家都把devtool的值设置为eval-source-map
    devtool: 'eval-source-map',
    //省略其它配置项...
    }
  3. webpack 生产环境下的 Source Map
    在生产环境下,如果省略了devtool选项,则最终生成的文件中不包含Source Map。这能够防止原始代码通过Source Map的形式暴露给别有所图之人

  4. 只定位行数不暴露源码 – 对调试和安全都比较友好
    在生产环境下,如果只想定位报错的具体行数,且不想暴露源码。此时可以将devtool的值设置为nosources-source-map
    知道了行号,我们就可以定位到源码的行位置来解决bug

  5. 定位行数且暴露源码
    在生产环境下,如果想在定位报错行数的同时,展示具体报错的源码。此时可以将devtool的值设置为source-map。
    坚决不推荐。(但是这不是和eval-source-map一样吗??)
    这种方式会生成一个SourceMap(但是nosources-source-map也有,eval-soure-map应该也有吧)
    可以看到在dist的js目录下,除了有bundle.js还有个bundle.js.map,这就是我们的SourceMap

Source Map的最佳实践

  1. 开发环境下:
    • 建议把devtool的值设置为eval-source-map
    • 好处: 可以精准定位到具体的错误行
  2. 生产环境下:
    • 建议关闭Source Map 或 将devtool的值设置为nosources-source-map
    • 好处: 防止源码泄露,提高网站的安全性

拓展

快速终止服务器

两次 ctrl + c 可以直接结束 webpack serve,不需要再去输入y结束
Vue CLI也一样,因为它是内置了webpack

webpack中 @ 的原理和好处

这样一个场景:
src目录下 有一个info文件 js/text/info
而这个info文件要导入src目录下的msg.js文件
就需要这样导入: import msg from ‘../../msg’
如果info的文件层级再深一点,这个../会让人不知道翻了几层
所以
建议大家都使用 @ 符号表示src源代码目录,从外往里查找,不要使用../从里往外查找
import msg from ‘@/msg.js’

在webpack里面,@不能直接用,需要先配置一下
在webpack.config.js中,module.exports中增加一个resolve属性:

1
2
3
4
5
6
7
8
module.exports = {
resolve:{
alias: {
// 告诉webpack,程序员写的代码中,@符号表示src这一层目录
'@': path.join(__dirname,'./src/')
}
}
}

在浏览器安装vue-devtools调试工具

edge扩展中直接搜,有一个vue.js-devtools
在详情设置中,有个允许访问文件URL(文件网址)的设置,默认是关闭的状态,需要我们手动打开

实际开发中不需要自己配置webpack

  • 实际开发中会使命令行工具(俗称CLI)一键生成带有webpack的项目
  • 开箱即用,所有webpack配置项都是现成的!
  • 我们只需要知道webpack 中的基本概念即可!