前端工程化之 webpack <一>

一、 Webpack 和打包过程

编写的代码 ==》经过打包工具(glup、rollup、webpack、vite)本身也是js代码,读取文件操作的,依赖于 node 环境

= = 》 普通的html 、css 、javascript

= = 》 打包到静态服务器

= = 》 跑在用户的浏览器

1.1 内置模块 path

  • 用于对路径和文件进行处理
  • 在 Mac OS、Linux 和 window 上的路径上是不一样的,部署的时候显示路径会出现一些问题,为了屏蔽他们之间的差异,在开发中对于路径的操作可以使用 path模块
const path = require("path");

// 帮助我们从路径当中获取一些信息
const filepath = "C://abc/cba/nba.txt";

// 1. 可以从一个路径中获取一些信息
// 1.1 拿到文件的后缀名
console.log(path.extname(filepath));
// 1.2 获取文件名
console.log(path.basename(filepath));
// 1.3 获取文件夹
console.log(path.dirname(filepath));

const path1 = "/abc/cba";
const path2 = "../../lili/kobe/james.txt";
// 2. 将多个路径拼接在一起
console.log(path.join(path1, path2));
// 3. 将多个路径拼接在一起,最终返回一个绝对路径: path.resolve
console.log("-----resolve------");
// 从右向左依次处理,形成了绝对路径就不会继续往前找了
console.log(path.resolve(path1, path2,"/abc.txt"));

1.2 认识 webpack

  • 随着前端的快速发展,目前前端的开发已经越来越复杂
    • 模块化的方式进行开发
    • 使用高级的特性来加快我们的开发效率或者安全性
    • 实时监听文件的变化,反映到浏览器上,提高开发的效率
    • 将代码进行压缩、合并以及其他相关的优化
  • 脚手架依赖 webpack
    • vue:cli-service
    • react:config
    • angular
  • webpack is a static module bundler for modern javascript application
    • 静态的:打包成普通的静态文件:html 、 css 、js
    • 模块化:common.js、amd 、cmd、
    • 现代的:应用程序复杂,才催生了webpack
    • 打包工具:本质是帮我们进行打包的

1.3 vue 项目加载的文件

  • javascript 的打包
    • 将 es6 转成es5
    • ts 转成 js
  • css 的处理
    • css 文件模块的加载、提取
    • less、sass 等预处理器的处理
  • 资源文件 img、font
    • 字体 font 文件的加载
    • 图片 img 文件的加载
  • html 资源的处理
    • 打包 html 资源文件
  • 处理 vue 项目的 sfc 文件 .vue 文件

1.4 webpack 的使用

  • webpack 、webpack-cli
    • webpack-cli:识别命令行
  • 局部安装npm i webpack webpack-cli -D
  • 使用局部的npx webpack后面可以跟参数
  • 配置参数很多,可以写到一个单独的文件里面:webpack.config.js – webpack 配置文件
const path = require("path");
module.exports = {
  // 配置入口
  entry: "./src/index.js",
  // 配置出口
  output: {
    filename: "bundle.js",
    // 必须是绝对路径
    path: path.resolve(__dirname, "./build"),
  },
};

1.5 webpack 形成的依赖图

  • 从入口开始,会生成一个依赖关系图,会包含应用程序中所需的所有模块(.js文件、css文件、图片、字体)
  • 遍历图结构,打包一个个模块
  • 根据文件的不同使用不同的 loader 来解析

1.6 loader

  • loader
    • loader 可以用于对模块的源代码进行转换
    • 将 css 文件也看成是一个模块,通过 import 来加载这个模块
    • 在加载这个模块的时候,webpack 其实并不知道如何进行加载,必须制定相应的规则去完成这个功能
    • 配置文件 =》module =》rules
      • 匹配 .vue=>使用 vue.loader
      • 匹配 .css=>使用 css.loader
  • 默认 webpack 会处理 js 文件
  • 配置方式
    • module{}
      • rules[]
        • {
        • test
        • use:多个 loader 的使用规则是从后往前的
        • }

1.7 css-loader

  • 安装:npm i css-loader -D
  • 配置规则
  module: {
    // 规则很多,对应的是数组类型
    rules: [
      // 数组里面放对象
      {
        // 告诉webpack 要匹配什么文件
        test: /.css$/,
        // 要使用哪些loader
        use: [{ loader: "css-loader" }],
      },
    ],
  },
  • 只负责解析 css 文件,并不负责插入到页面中

1.8 style-loader

  • 插入 style
  • 安装:npm i style-loader -D
const path = require("path");
module.exports = {
  // 配置rukou
  entry: "./src/index.js",
  // 配置出口
  output: {
    filename: "bundle.js",
    // 必须是绝对路径
    path: path.resolve(__dirname, "./build"),
  },
  module: {
    // 规则很多,对应的是数组类型
    rules: [
      // 数组里面放对象
      {
        // 告诉webpack 要匹配什么文件
        test: /.css$/,
        // 要使用哪些loader
        use: [{ loader: "style-loader" }, { loader: "css-loader" }],
      },
    ],
  },
};

  • 简写
   test: /.css$/,
        // 要使用哪些loader
        // use: [{ loader: "style-loader" }, { loader: "css-loader" }],
        // 简写一:如果 Loader 只有一个
        // loader: 'css-loader'
        // 简写二:多个loader不需要配置其他options
        use: ["style-loader", "css-loader"],

1.9 less-loader

  • 前提需要安装 less
  • 安装:npm i less-loader -D
  • 配置
     {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },

1.10 postcss-loader

  • 安装:npm i postcss-loader -D

  • 使用:use: ["style-loader", "css-loader", "postcss-loader"]

  • postcss 介绍

    • 通过 javascript 来转换样式的工具
    • 这个工具可以帮助我们进行一些 css 的转换和适配,比如自动添加浏览器前缀、css样式的重置
    • 需要借助 对应的插件
      • 安装:npm i autoprefixer -D
  • 重新配置

    • options 里面添加的东西会被 loader 读取
     {
            // 告诉webpack 要匹配什么文件
            test: /.css$/,
            use: [
              "style-loader",
              "css-loader",
              {
                loader: "postcss-loader",
                options: {
                  postcssOptions: {
                    plugins: [
                      "autoprefixer"
                    ]
                  },
                },
              },
            ],
          },
    
    • 这样就能实现添加浏览器前缀
  • 太臭太长了,在项目目录中里面创建文件postcss.config.js

    • 当写成简写的时候没有配置对应的 options,就会自己读取这个文件
    module.exports = {
      plugins: ["autoprefixer"],
    };
    
    
  • 因为有下面这个,所以卸载它:npm uninstall autoprefixer

  • 也可以使用另外的插件postcss-preset-env预设环境

    • 可以将一些现代的 css 特性,转成大多数浏览器认识的 css ,并且会根据目标浏览器或者运行时环境添加所需的 polyfill
      • bgc-color:#666666==》 rgba 的格式
    • 包括 autoprefixer
    • 安装:npm install postcss-preset-env -D

1.11 Webpack打包图片-JS-Vue

  • 现代的前端开发模式中,根本不需要手动添加浏览器前缀
  • 引入图片模块:import 图片名字 from "../img/zznh.png"
    • 可以给这个模块起一个名字
  • 早期加载资源使用:npm i file-loader
    • 在最新的版本已经不用这个东西了
    • webpack5之后使用资源模块类型开替代这些loader
      • raw-loader、url-loader、file-loader
  {
        test: /.(png|jpe?g|svg|gif)$/,
        // 当成资源类型
        type:"assets"
      },

1.12 资源模块类型

  • asset/resource:发送一个单独的文件并导出 URL
    • 缺点:多了 http 网络请求(几张图片就几次)
  • asset**/inline**:导出一个资源的 data URL
    • 优点:可以少发送两次请求
    • 缺点:造成 js 文件很大,下载 、解析js 文件时间非常长
  • asset/source:导出资源的源代码
  • asset
    • 使用过程
      • 将 type 改成 asset
      • 添加 parser 属性,并且制定 dataUrl 的条件
        • maxSize
const { type } = require("os");
const path = require("path");
const { webpack } = require("webpack");
module.exports = {
  // 配置rukou
  entry: "./src/index.js",
  // 配置出口
  output: {
    filename: "bundle.js",
    // 必须是绝对路径
    path: path.resolve(__dirname, "./build"),
    // assetModuleFilename: "abc.png",//很少在这配置
  },
  module: {
    // 规则很多,对应的是数组类型
    rules: [
      {
        test: /.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.(png|jpe?g|svg|gif)$/,
        // 当成资源类型
        // type: "asset"
        // 1. 打包两张图片,并且这两张图片有自己的地址,将地址设置到img/bgi中
        // type:"asset/resource"
        // 2. 把图片进行 base64 编码,并且直接将编码后的源码放在打包的js文件中

        // type: "asset/inline"

        // 合理的规范
        // 1 对于小一点的图片,可以进行base64的编码
        // 2 对于大一点的图片,单独的图片打包,形成 url 地址,单独的请求这个 url 文件
        // 由此诞生了asset
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 60 * 1024,
          },
        },
        // 设置文件名
        generator: {
          // 一般名字不写死,写上占位符
          // name:指向原来的图片名称
          // ext(扩展名):直接带点
          // hash:webpack生成的hash,只使用前八位
          // 放在build的img 文件夹
          filename: "img/[name]_[hash:8][ext]",
        },
      },
    ],
  },
};

1.13 babel

  • 想要使用 ES6+ 的语法,使用 ts ,开发 react,都是离不开 babel 的
  • 安装:npm i babel-loader -D
  {
        test: /.js$/,
        use: ["babel-loader"],
      },
  • babel 是一个工具链,用于旧浏览器或者环境中将 es5 代码转换为向后兼容版本的 js
    • 语法转换:箭头函数
    • 源代码转换:对标识符进行转化
  • 本身可以作为一个独立的工具,不和webpack等构建工具来单独使用
  • 命令行使用,需要安装
    • @babel/core:babel 的核心代码,必须安装
    • @babel/cli:在命令行使用 babel
    • npm i @babel/cli @babel/core -D
  • 安装插件
    • 转换对应的东西
    • 每个插件完成对应的功能
  • 单独抽取出配置:babel.config.js
module.exports = {
  // plugins: [
  //   "@babel/transform/"
  // ]
  presets: ["@babel/preset-env"],
};

  • 单独配置太麻烦了,安装预设:npm i @babel/preset-env -D
    • 常见的预设
      • env
      • react
      • TypeScript

.vue

  • 安装 vue :npm i vue开发生产都会使用
  • 安装loader:npm i vue-loader -D
   {
        test: /.vue$/,
        loader:"vue-loader"
      }
  • vue文件中配置对应的 vue 插件:帮助我们解析 css
const { VueLoaderPlugin } = require('vue-loader/dist/index')
const path = require("path");
const { webpack } = require("webpack");
module.exports = {
  // 配置rukou
  entry: "./src/index.js",
  // 配置出口
  output: {
    filename: "bundle.js",
    // 必须是绝对路径
    path: path.resolve(__dirname, "./build"),
    // assetModuleFilename: "abc.png",//很少在这配置
  },
  module: {
    // 规则很多,对应的是数组类型
    rules: [
      {
        test: /.vue$/,
        loader:"vue-loader"
      }
    ],
  },
  plugins: [
    new VueLoaderPlugin()
  ]
};

1.14 resolve 模块解析

  • 用于设置模块如何被解析

    • 帮助 webpack 从每个 require/import 语句中,找到需要引入到合适的模块代码
  • webpack 能解析三种文件路径

    • 绝对路径
    • 相对路径
    • 模块路径
      • resolve.modules 中指定的所有目录检索模块:默认为 node_modules
      • 设置别名
  • 解析

    • 文件:imprt utils form './utils/format'js/json

      • 具备扩展名,直接打包文件

      • 否则使用 resolve.extentions 自动添加扩展名

        • 默认值是 [‘.wasm’,‘’.mjs’,‘.js’,‘.json’]
      • 可以自己配置 resolve

      • resolve: {
           // 配置后可省略后缀名
           extensions:['.js','.json','.vue','.jsx','.ts','.tsx']
         },
        
    • 文件夹:

      • 在文件夹中根据 resolve.mainFiles 配置选项中指定的文件顺序查找
      • 默认值是 [‘index’]
      • 再根据 resolve.extentions 来解析扩展名
  • alias

    • 给文件夹配置别名,解决嵌套层次深的问题
  resolve: {
    // 省略后缀名
    extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
    // 给文件夹配置别名
    alias: {
      utils: path.resolve(__dirname, "./src/utils"),
    },
  },

二、webpack 常见的插件和模式

2.1 plugin

  • loader 是用于特定类型的模块类型进行转换
    • .vue .css .png
  • Plugin 作用广泛的任务,可以做 loader 之外的所有事情
    • 打包优化
    • 资源管理
    • 环境变量注入

2.2 CleanWebpackPlugin

  • 作用:每次修改配置重新打包时候,不需要手动删除 dist 文件夹
  • 安装这个插件:npm i clean-webpack-plugin -D
  • 在插件中进行配置
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
  plugins: [new VueLoaderPlugin(),new CleanWebpackPlugin()],
  • 也可以直接在output中将 clear 设置为true
  output: {
    filename: "bundle.js",
    // 必须是绝对路径
    path: path.resolve(__dirname, "./build"),
    // assetModuleFilename: "abc.png",//很少在这配置
    clear: true,
  },

2.3 HtmlWebpackPlugin

  • 在进行项目部署的时,必然也是需要有对应的入口文件 index.html,需要对 index.html 进行打包处理

  • 安装这个插件:npm i html-webpack-plugin -D

  • build 文件夹下面会自动生成 html 文件

  • 在生成过程会有一个模板,在 html-webpack-plugin 的源码中,有一个 default_index.ejs 模块

  • 也可以自定义模板

const HtmlWebpackPlugin = require("html-webpack-plugin");
  plugins: [
    new HtmlWebpackPlugin({
      title: "实时音频流可视化",
      // 自己指定模板
      template:'./index.html'
    }),
  ],

2.4 DefinePlugin

  • webpack 中内置的插件

  • 全局注入变量

  • 默认注入了变量:process.env.NODE_ENV

  • 可以使用 DefinePlugin 中定义的变量

const { DefinePlugin } = require("webpack");
 plugins: [
    new DefinePlugin({
      // 会当成代码去解析,所以需要加引号(eval)
      // 前边是变量,随便你加不加引号
      BASE_URL: "'./'",
      lili: "'lili'",
    }),
  ],

2.5 Mode

  • 告诉 webpack目前所处的一个环境
  • 默认值是 production
  • 可选值有:none| development|production
  • 每一个模式涉及很多配置
  • 配置: mode:production,

三、Webpack 搭建本地服务器

3.1 作用

  • 修改代码后自动重新打包并且自动刷新
  • 自动重新编译:
    • webpack watch mode:可以自动编译但是不会自动刷新浏览器
    • webpack-dev-server(常用)
    • webpack-dev-middleware

3.2 webpack-dev-server

  • 安装:npm i webpack-dev-server -D
  • 会搭建一个本地服务
  • 会自动进行打包,但是不会生成本地的文件,直接放在内存里面,直接给你搭建服务器,从内存中读取

3.3 模块热替换 HMR

  • 在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面
  • 提高开发的速度
    • 不重新加载整个页面
    • 只更新需要变化的内容
  • 修改 webpack 的配置,但是还是刷新一整个页面
  devServer: {
    hot:true
  },
  • 需要在 main.js 手动指定哪些模块发生更新时,进行HMR

    • 先判断是否开启 HMR
    • 再告诉他那个模块更新需要发生替换
    if (module.hot) {
      module.hot.accpet("./utils/math.js", () => {
        console.log("happen hot gengxin");
      })
    }
    
  • 框架已经集成好了,不需要手动配置

## 3.4 host

  • 设置主机地址,默认值是 localhost
  • 看 ip 地址:网络=》属性
  • 设置为 0.0.0.0 都可以看到

3.5 open

  • true / false : 是否自动打开浏览器
  devServer: {
    hot: true,
    // 端口位置
    port: 8888,
    // host设置主机地址,默认值是 localhost 
    host: '0.0.0.0',
    open: ture,
    // 自动把打包的文件进行gzip压缩
    compress: true 
  },

3.6 区分开发环境和生产环境

  • 打包适用于生产环境

  • 创建 config 文件夹

    • webpack.dev.config.js
    • webpack.prod.config.js
  • 使用 serve / build 是不同的两套配置

  • context 的作用:解析入口和加载器

    • 默认路径是 webpack 的启动路径(脚本中配置了)
     "build": "webpack --config ./config/webpack.prod.config.js",
        "serve": "webpack serve --config ./config/webpack.dev.config.js"
    
    // 配置入口
    // context:'',
    // 相对于 context
    entry: "./src/index.js",
    
  • 很多配置是相似的

    • 抽取公共配置:webpack.comm.config.js
      • 哪些是特有的
    • 安装插件进行合并:npm i webpack-merge -D
  • webpack 跑在 node 当中,node 支持 commonjs

  • prod

const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.comm.config");
module.exports = merge(commonConfig, {
  mode: production,
  output: {
    clean: true,
  },
});
  • dev
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.comm.config')
module.exports = merge(commonConfig,{
  mode: development,
  devServer: {
    hot: true,
    // 端口位置
    port: 8888,
    // host设置主机地址,默认值是 localhost
    host: "0.0.0.0",
    open: ture,
    // 自动把打包的文件进行gzip压缩
    compress: true,
  },
})
  • vue
    • 把所有和 webpack 有关的放在了 service-cli 里面

四、补充

4.1 命令行

  1. 回到上一层目录:cd …
  2. 清空命令行:cls
  3. 切换盘符:f:

4.2 开发

  1. 分享代码:删除node_modules发给同事

五、Node跨域

5.1 跨域

  • 浏览器的同源策略:
    • 同源策略是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
    • 如果两个 URL 的 protocol、port (en-US) (如果有指定的话) 和 host 都相同的话,则这两个 URL 是同源
    • 这个方案也被称为“协议/主机/端口元组”,或者直接是“元组”。
  • 跨域的产生和前端分离的发展有很大的关系
    • 早期的服务器端渲染的时候,是没有跨域的问题的
    • 但是随着前后端的分离,目前前端开发的代码和服务器开发的 API 接口往往是分离的,甚至部署在不同的服务器上的
  • 访问静态资源服务器 和 API 接口服务器 很有可能不是同一个服务器或者不是同一个端口
  • 浏览器发现静态资源和 API 接口(XHR、Fetch)请求不是来自同一个地方时(同源策略),就产生了跨域
  • 静态资源服务器和 API 服务器(其他资源类同)是同一台服务器时,是没有跨域问题的

5.2 跨域解决

  • 跨域的解决方案几乎都和服务器有关系,单独的前端基本解决不了跨域
  • webpack 配置的本质也是在 webpack-server 的服务器中配置了代理
  • 解决方案
    • 方案一:静态资源和 API 服务器部署在同一个服务器中
    • 方案二:服务器中开启CORS, 即是指跨域资源共享
    • 方案三:node 代理服务器(webpack中就是它,开发阶段使用)
    • 方案四:Nginx 反向代理(真正部署的时候使用)
  • 不常用:
    • jsonp:现在很少使用了(曾经流行过一段时间)
    • postMessage
    • websocket:为了解决跨域,所有的接口都变成 socket 通信

5.3 前端请求代码

  <script>
    // 1.XHR网络请求
    const xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function() {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        console.log(JSON.parse(xhr.responseText))
      }
    }
    xhr.open('get', 'http://localhost:8000/users/list')
    xhr.send()


    // 2.fetch网络请求
    fetch('http://localhost:8000/users/list').then(async res => {
      const result = await res.json()
      console.log(result)
    })
  </script>

5.4 CORS

  • 跨源资源共享(CORS, Cross-Origin Resource Sharing 跨域资源共享)
    • 它是一种基于 http header 的机制
    • 该机制通过允许服务器标示除了它自己以外的其它源(域、协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。
  • 浏览器将 CORS 请求分成两类:简单请求和非简单请求。
  • 只要同时满足以下两大条件,就属于简单请求(不满足就属于非简单请求)
    • 请求方法是以下是三种方法之一
      • HEAD
      • GET
      • POST
    • HTTP 的头信息不超出以下几种字段:
      • Accept
      • Accept-Language
      • Content-Language
      • Last-Event-ID
      • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

六、模块化原理 - source-map

6.1 source-map

  • 安装:npm init -y npm i webpack webpack-cli -D

  • mode

    • 看起来设置了一个属性,实际上有更多的一个配置
    • devtool:“eval”
      • 设置 source-map
  • 代码通常运行在浏览器上时,是通过 打包压缩 的:

    • 也就是真实跑在浏览器上的代码,和编写的代码其实是有差异的
    • 比如 ES6 的代码可能被转换成 ES5
    • 比如对应的代码行号、列号在经过编译后肯定会不一致
    • 比如代码进行丑化压缩时,会将编码名称等修改
    • 比如使用了 TypeScript 等方式编写的代码,最终转换成 JavaScript
  • 但是,当代码报错需要调试时(debug),调试转换后的代码是很困难的

  • 但是能保证代码不出错吗?不可能。

  • 那么如何可以调试这种转换后不一致的代码呢?答案就是 source-map

    • source-map 是从已转换的代码,映射 到原始的源文件
    • 使浏览器可以重构原始源并在调试器中显示重建的原始源

6.2 使用 source-map

  • 配置: devtool:'source-map',
  • 使用:
    • 第一步:根据源文件,生成 source-map文件,webpack 在打包时,可以通过配置生成source-map
    • 第二步:在转换后的代码,最后添加一个注释,它指向 sourcemap
      • //# sourceMappingURL=common.bundle.js.map
      • 浏览器会根据注释,查找相应的 source-map ,并且根据 source-map 还原代码,方便进行调试

6.3 分析 source-map

  • 最初 source-map生成的文件大小是原始文件的10倍,第二版减少了约50%,第三版又减少了50%,所以目前一个133kb的文件,最终的 source-map 的大小大概在300 kb
  • 目前的 source-map 长什么样子
    • version:当前使用的版本,也就是最新的第三版
    • sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件)
    • names:转换前的变量和属性名称(目前使用的是development模式,所以不需要保留转换前的名称)
    • mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable-length quantity可变长度值)编码
    • file:打包后的文件(浏览器加载的文件)
    • sourceContent:转换前的具体代码信息(和sources是对应的关系)
    • sourceRoot:所有的sources相对的根目录
  • 帮助进行调试

6.4 生成 source-map

  • 在使用 webpack打包的时候,生成对应的source-map
    • webpack 提供了非常多的选项(目前是26个),来处理 source-map
    • https://webpack.docschina.org/configuration/devtool/
    • 选择不同的值,生成的 source-map 会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择
  • 下面几个值不会生成 source-map
    • false不使用 source-map,也就是没有任何和 source-map 相关的内容。
    • noneproduction 模式下的默认值(什么值都不写) ,不生成 source-map。
    • eval:development 模式(开发模式)下的默认值,不生成 source-map
      • 但是它会在 eval 执行的代码中,添加 //# sourceURL=
      • 它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便调试代码
      • 可以还原源代码,但是记录的信息没有那么准确

## 6.5 source-map 值

  • 一般在 production 文件设置
  • source-map
    • 生成一个独立的 source-map文件,并且在 bundle 文件中有一个注释,指向 source-map文件
  • bundle文件中有如下的注释://# sourceMappingURL=bundle.js.map
    • 开发工具会根据这个注释找到 source-map文件,并且解析

6.6 设置

  • production:默认为 none,不需要设置
  • development:默认为 eval
  • production:source-map
    • 上线之后再做调试需要使用

6.7 eval-source-map 值

  • eval-source-map:会生成 sourcemap,但是 source-map是以 DataUrl 添加到 eval 函数的后面

6.8 inline-source-map 值

  • inline-source-map:会生成 sourcemap,但是 source-map是以 DataUrl 添加到 bundle 文件的后面

6.9 cheap-source-map

  • 会生成 sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)
  • 因为在开发中,只需要行信息通常就可以定位到错误了
  • 需要用到开发环境中:mode:development

6.10 cheap-module-source-map

  • 会生成 sourcemap,类似于 cheap-source-map,但是对源自 loader 的 sourcemap 处理会更好

  • 需要用到开发环境中:mode:development

6.11 hidden-source-map

  • 需要用到生产环境中:mode:production
  • 会生成 sourcemap,但是不会对 source-map文件进行引用,浏览器没有下载
  • 相当于删除了打包文件中对 sourcemap 的引用注释
  • 手动添加
// 被删除掉的
//# sourceMappingURL=bundle.js.map

6.12 nosources-source-map

  • 会生成 sourcemap,但是生成的 sourcemap 只有错误信息的提示,不会生成源代码文件
  • 点击错误信息,但是点开并没有

6.13 多个值的组合

  • 事实上,webpack 提供的26个值,是可以进行多组合的
  • 组合的规则如下:
    • inline-|hidden-|eval:三个值时三选一
    • nosources:可选值
    • cheap可选值,并且可以跟随module的值;
  • 那么在开发中,最佳的实践
    • 开发阶段:推荐使用 source-map 或者 cheap-module-source-map
      • 这分别是vue和react使用的值,可以获取调试信息,方便快速开发
    • 测试阶段:推荐使用 source-map 或者 cheap-module-source-map
      • 测试阶段也希望在浏览器下看到正确的错误提示
    • 发布阶段:false、缺省值(不写)

七、深入 Babel - polyfill

7.1 Babel - polyfill

  • webpack 底层就是使用 babel 转换代码

  • Babel是一个工具链,主要用于旧浏览器或者环境中将 ECMAScript 2015+ 代码转换为向后兼容版本的 JavaScript

  • 包括:语法转换、源代码转换、Polyfill 实现目标环境缺少的功能等

7.2 Babel 命令行使用

  • babel 本身可以作为一个独立的工具(和 postcss 一样),不和 webpack 等构建工具配置来单独使用
  • 在命令行使用 babel,需要安装如下库:npm install @babel/cli @babel/core
    • 安装插件:
      • npm i @babel/plugin-transform-block-scoping -D
      • npm i @babel/plugin-transform-arrow-functions -D
  • 使用 babel 来处理源代码:npx babel src --out-dir dist
    • src:源文件的目录 ./src
    • –out-dir:要输出的文件夹 dist ./build

7.3 Babel 的预设 preset

  • 如果要转换的内容过多,一个个设置是比较麻烦的,可以使用预设(preset)
  • 安装@babel/preset-env预设:npm install @babel/preset-env -D
  • 执行如下命令:npx babel src --out-dir dist --presets=@babel/preset-env

7.4 Babel 的底层原理

  • babel 将一段代码(ES6、TypeScript、React)转成另外一段代码(ES5)

    • 从一种源代码(原生语言)转换成另一种源代码(目标语言)
    • 就是编译器的工作,事实上可以将 babel 看成就是一个编译器
  • Babel 编译器的作用就是将源代码转换成浏览器可以直接识别的另外一段源代码

  • Babel 也拥有编译器的工作流程

    • 解析阶段(Parsing)
    • 转换阶段(Transformation)
    • 生成阶段(Code Generation)
  • 使用:如果没有webpack 需要单独敲对应的命令:npx babel

    • 结合 webpack 进行使用:npm i webpack webpack-cli -D
  • webpack 对应的是模块化的内容,babel 对应的是es6 转化为es5的内容–两者结合在一起

7.5 babel-loader

  • 安装:npm install babel-loader @babel/core -D
    • @babel/core 会自动安装
  • 必须指定使用的插件才会生效
  module: {
    rules: [
      {
        test: /.js$/,
        use: {
          loader: "babel-loader", //会自动去找对应的 babel 工具
          options: {
            // plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-block-scoping"],
            presets: ["@babel/preset-env"],
          },
        },
      },
    ],
  },
  • webpack 与 对应的另外一个工具结合起来都是使用对应的 loader

7.6 babel-preset

  • **可以直接给 webpack 提供一个 preset,**webpack会根据预设来加载对应的插件列表,并且将其传递给 babel
  • 常见的预设有三个
    • env:适配的环境
    • react:对 react 代码进行转换
    • TypeScript
  • 安装:npm install @babel/preset-env

7.7 浏览器兼容性

  • 兼容性:针对不同的浏览器支持的特性:比如 css 特性、js 语法之间的兼容性

  • 市面上有大量的浏览器:

    • 有Chrome、Safari、IE、Edge、Chrome for Android、UC Browser、QQ Browser等等
  • 其实在很多的脚手架配置中,都能看到类似于这样的配置信息:

    • 这里的百分之一,就是指市场占有率

    • > 1%
      last 2 versions
      not dead
      
  • 代码要不要自动转换取决于要适配的浏览器

  • 浏览器市场占有率caniuse

7.8 browserslist 工具

  • 帮助去配置需要兼容的浏览器版本
  • Browserslist 是一个在不同的前端工具之间,共享目标浏览器和 Node.js 版本的配置:
    • Autoprefixer:自动添加浏览器前缀
    • Babel
    • postcss-preset-env
    • eslint-plugin-compat
    • stylelint-no-unsupported-browser-features
    • postcss-normalize
    • obsolete-webpack-plugin
  • 条件查询使用的是 caniuse-lite 的工具,这个工具的数据来自于 caniuse 的网站上
  • 工程化:很多操作不需要自己来做,甚至不需要自己来配置,利用一系列的工具去做这件事情
  • 编写规则
    • defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)
      • not dead:24 个月之内都没有官网更新和支持
    • 5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过
      • 5% in US:使用美国使用情况统计信息。它接受两个字母的国家/地区代码
      • 5% in alt-AS:使用亚洲地区使用情况统计信息。有关所有区域代码的列表,请参见caniuse-lite/data/regions
      • 5% in my stats:使用自定义用法数据
      • 5% in browserslist-config-mycompany stats:使用 来自的自定义使用情况数据browserslist-config-mycompany/browserslist-stats.json
      • cover 99.5%:提供覆盖率的最受欢迎的浏览器
      • cover 99.5% in US:与上述相同,但国家/地区代码由两个字母组成
      • cover 99.5% in my stats:使用自定义用法数据
    • dead:24个月内没有官方支持或更新的浏览器。现在是IE 10,IE_Mob 11,BlackBerry 10,BlackBerry 7, Samsung 4和OperaMobile 12.1
    • last 2 versions:每个浏览器的最后2个版本
      • last 2 Chrome versions:最近2个版本的Chrome浏览器
      • ast 2 major versions或last 2 iOS major versions:最近2个主要版本的所有次要/补丁版本
    • node 10和node 10.4:选择最新的Node.js10.x.x 或10.4.x版本
      • current node:Browserslist现在使用的 Node.js 版本
      • maintained node versions:所有Node.js 版本,仍由 Node.js Foundation 维护
    • iOS 7:直接使用iOS浏览器版本7
      • Firefox > 20:Firefox的版本高于20 >=,<并且<=也可以使用。它也可以与Node.js一起使用。
      • ie 6-8:选择一个包含范围的版本。
      • Firefox ESR:最新的[Firefox ESR]版本
      • PhantomJS 2.1和PhantomJS 1.9:选择类似于PhantomJS运行时的Safari版本
    • extends browserslist-config-mycompany:从browserslist-config-mycompanynpm包中查询 。
    • supports es6-module:支持特定功能的浏览器
      • es6-module这是“我可以使用” 页面feat的URL上的参数。有关所有可用功能的列表,请参见 :caniuse-lite/data/features
    • **browserslist config:在Browserslist配置中定义的浏览器。**在差异服务中很有用,可用于修改用户的配置,例如 browserslist config and supports es6-module。
    • since 2015或last 2 years:自2015年以来发布的所有版本(since 2015-03以及since 2015-03-10)
    • unreleased versions或unreleased Chrome versions:Alpha和Beta版本
    • not ie <= 8:排除先前查询选择的浏览器
  • 命令行使用,自己添加规则:npx browserslist ">1%, last 2 version, not dead",真实开发不会使用

7.9 配置browserslist

  • 查看是否安装:node_modules
  • 写法一:在package.json中配置: "browserslist":[]
  • 写法二:单独的一个配置文件 .browserslistrc 文件,写上对应的规则

7.10 设置目标浏览器 browserslist/ targets

  • 最终打包的 JavaScript 代码,是需要跑在目标浏览器上的,那么如何告知 babel 目标浏览器
    • browserslist 工具
    • target 属性(一般不使用)
  • 配置的 targets 属性会覆盖 browserslist
  • 但是在开发中,更推荐通过 browserslist来配置,因为类似于 postcss工具,也会使用 browserslist,进行统一浏览器的适配

7.11 Stage-X 的 preset

  • TC39 的组织
    • TC39是指技术委员会(Technical Committee)第 39 号
    • 它是 ECMA 的一部分,ECMA 是 “ECMAScript” 规范下的 JavaScript 语言标准化的机构
    • ECMAScript 规范定义了 JavaScript 如何一步一步的进化、发展
  • TC39 遵循的原则是:分阶段加入不同的语言特性,新流程涉及四个不同的 Stage
    • **Stage 0:**strawman(稻草人,图纸),任何尚未提交作为正式提案的讨论、想法变更或者补充都被认为是第 0 阶段的"稻草人"
    • **Stage 1:**proposal(提议),提案已经被正式化,并期望解决此问题,还需要观察与其他提案的相互影响
    • **Stage 2:**draft(草稿),Stage 2 的提案应提供规范初稿、草稿。此时,语言的实现者开始观察 runtime 的具体实现是否合理
    • **Stage 3:**candidate(候补),Stage 3 提案是建议的候选提案。在这个高级阶段,规范的编辑人员和评审人员必须在最终规范上签字。Stage 3 的提案不会有太大的改变,在对外发布之前只是修正一些问题
    • **Stage 4:**finished(完成),进入 Stage 4 的提案将包含在 ECMAScript 的下一个修订版中
  • Babel的 Stage-X 设置,但是从babel7开始,已经不建议使用了,建议使用preset-env 来设置

7.12 Babel 的配置文件

  • babel 配置越多的话,不利于维护,建议创建一个配置文件
  • 可以将 babel 的配置信息放到一个独立的文件中,babel 提供了两种配置文件的编写:
    • babel.config.json(或者.js,.cjs,.mjs)文件;
    • 喜欢以 js 结尾
    • .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;
  • 它们两个有什么区别呢?目前很多的项目都采用了多包管理的方式(babel本身、element-plus、umi等)
    • .babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;
    • babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐

7.13 polyfill

  • Polyfill

    • 翻译:一种用于衣物、床具等的聚酯填充材料, 使这些物品更加温暖舒适
    • 理解:更像是应该填充物(垫片),一个补丁,可以帮助更好的使用 JavaScript
  • 为什么会用到 polyfill

    • 比如使用了一些语法特性(例如:Promise, Generator, Symbol 等以及实例方法(特殊的 api )例如 Array.prototype.includes 等)
    • 但是某些浏览器压根不认识这些特性,必然会报错
    • 可以使用 polyfill 来填充或者说打一个补丁,那么就会包含该特性了
  • 使用

    • babel7.4.0 之前,可以使用 @babel/polyfill的包,但是该包现在已经不推荐使用了

    • babel7.4.0 之后,可以通过单独引入 core-js 和 regenerator-runtime 来完成 polyfill 的使用,安装:

      • 开发生产环境都要使用
      • npm install core-js regenerator-runtime
    • 配置 babel.config.js

      • useBuiltIns:设置以什么样的方式来使用 polyfill
        • false
          • 打包后的文件不使用 polyfill 来进行适配
          • 并且这个时候是不需要设置 corejs 属性的
        • **usage **(推荐使用)
          • 会根据源代码中出现的语言特性,自动检测所需要的 polyfill
          • 这样可以确保最终包里的 polyfill 数量的最小化,打包的包相对会小一些
          • 可以设置 corejs 属性来确定使用的 corejs 的版本
        • entry
          • 如果依赖的某一个库/第三方代码本身使用了某些 polyfill 的特性,但是因为使用的是 usage,所以之后用户浏览器可能会报错
          • 所以,如果担心出现这种情况,可以使用 entry
          • 并且需要在入口文件(index.js)中添加 `import ‘core-js/stable’; import ‘regenerator-runtime/runtime’
          • 这样做会根据 browserslist 目标导入所有的 polyfill,但是对应的包也会变大
      • corejs:设置 corejs 的版本,目前使用较多的是 3.x 的版本,可以在 package.json 文件中进行查看
        • corejs 可以设置是否对提议阶段的特性进行支持
        • 设置 proposals 属性为 true
      module.exports = {
        // plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-block-scoping"],
        presets: [
          [
            "@babel/preset-env",
            {
              // 指定使用版本
              corejs: 3,
              useBuiltIns:false
            },
          ],
        ],
      };
      
      

7.14 React 的 jsx 支持

  • 编写 react 代码时,react 使用的语法是 jsx,jsx 是可以直接使用 babel 来转换的

  • 安装 react :npm i react react-dom

  • 安装 preset :npm install @babel/preset-react -D

  • 挂载到 index.html 上,需要打包 html 文件:npm i html-webpack-plugin -D

  • const HtmlWebpackPlugin = require("HtmlWebpackPlugin ");
      plugins: [
        new HtmlWebpackPlugin({
          template: "./index.html",
        }),
      ],
    
  • module: {
      rules: [
        {
          test: /.jsx?$/, //x?:0或者1个
          use: {
            loader: "babel-loader", //会自动去找对应的 babel 工具
            options: {
              // plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-block-scoping"],
              presets: [["@babel/preset-env", {}], ["@babel/preset-react"]],
            },
          },
        },
      ],
    },
    

7.15 TypeScript 的编译

  • 可以通过 TypeScript 的 compiler 来转换成 JavaScript:npm install typescript -D

  • 通过 tsc --init 为 TypeScript 的编译配置信息编写一个 tsconfig.json 文件

  • 手动编译自己的ts代码:npx tsc

7.16 使用 ts-loader

  • 安装:npm install ts-loader -D
  • 在安装 ts-loader 的时候会自动安装 TypeScript 的 compiler
  • 配置
     {
        test: /.ts$/,
        use:'ts-loader'
      }
  • 通过 npm run build 打包
  • 来直接编译 TypeScript,那么只能将 ts 转换成 js
  • 还希望在这个过程中添加对应的 polyfill,那么 ts-loader 是无能为力的
  • 需要借助于 babel 来完成 polyfill 的填充功能

7.17 使用 babel-loader

  • 一般处理 ts 代码不会使用 ts-loader 进行处理,会选择 babel-loader 进行处理
    • ts-loader 需要单独安装 ts-loader
    • 编写代码过程也可能用到 polyfill,ts-loader 是没有 polyfill 的
  • 使用预设: npm install @babel/preset-typescript -D
  • 来直接编译TypeScript,也可以将 ts 转换成 js,并且可以实现 polyfill 的功能
  • babel-loader 在编译的过程中,不会对类型错误进行检测

7.18 编译 TypeScript 最佳实践

  • 使用 Babel 来完成代码的转换,打包代码

  • 使用 tsc 来进行类型的检查

    • 在 scripts 中添加了两个脚本,用于类型检查

      • 执行 npm run type-check可以对 ts 代码的类型进行检测:校验出代码有没有错误
      • 执行 npm run type-check-watch可以实时的检测类型错误