1. webpack 和 webpack-cli 的关系
webpack 的安装目前分为两个:webpack、webpack-cli
webpack、webpack-cli 的区别
控制台中执行 webpack 指令,会执行 node_modules 下的 .bin 目录下的 webpack 文件,webpack 的执行依赖 webpack-cli,如果没有安装 webpack-cli 就会报错。在 webpack-cli 中代码执行时,才是真正利用 webpack 进行编译和打包的过程。
npm install webpack webpack-cli –g #全局安装
npm install webpack webpack-cli –D #局部安装 (项目中使用)
全局安装 和 局部安装 的区别
局部安装:如果只有全局的 webpack,那么打包的时候,用了全局的 webpack,不同电脑的 webpack 版本不同会导致包的安装版本不同。
局部安装:每一个项目都有自己的 webpack 的版本, –D 是开发时依赖,定义了统一的 webpack 版本,打包的时候不会出现包的版本问题。
直接在命令行中执行 webpack 找的是全局的 webpack,如果想用局部的 webpack:
去 node_modules 中的 .bin 中找 webpack:./node_modules/.bin/webpack
执行npx webpack 默认找 node_modules 中的 .bin 下的 webpack 文件
在 package.json 中写脚本 "build": "npx webpack",在使用的时候 npm run build 相当于执行了 webpack 指令。
{
"name": "test",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.14.0",
"webpack-cli": "^4.3.1"
}
}
webpack 是如何确定入口
运行 webpack 时,webpack 会查找当前目录下的 src/index.js 作为入口,如果没有存在 src/index.js 文件,就会报错。
webpack 是如何对项目进行打包
根据命令或者配置文件找到入口文件,从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等)然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)
2. webpack 对样式的处理
css-loader
webpack 可以处理 js 文件,但是当代码中 import 引入了 css 文件,webpack 不知道如何对其进行加载。
- css-loader的安装:
npm install css-loader -D
- css-loader 的使用
module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
// 必须是一个绝对路径
path: path.resolve(__dirname, "./build")
},
module: {
rules: [
{
test: /\.css$/, // 匹配资源
use: [
{ loader: "css-loader" }
]
}
]
}
}
css-loader 只是负责将 .css 文件进行解析,并不会将解析之后的 css 插入到页面中,需要用 style-loader 完成插入样式的操作。
- 安装style-loader:
npm install style-loader -D - 配置 style-loader
{
test: /\.css$/, // 匹配资源
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
}
Less 处理
- 可以使用 less 工具来完成它的编译转换
- 安装:
npm install less -D - 执行:
npx less ./src/css/title.less > title.css
- less-loader 处理
- 安装:
npm install less-loader -D - 配置 webpack.config.js
{
test: /\.less$/,
use: [
"style-loader",
{
loader: "css-loader"
}
"less-loader"
]
}
3.browserslist
属性在浏览器市场占有率可以在Can I use... Support tables for HTML5, CSS3, etc 查看。
browserslist 工具
Browserslist是一个 在不同的前端工具之间 ,共享 目标浏览器和Node.js版本的配置 :
- Autoprefixer
- Babel
- postcss-preset-env
- eslint-plugin-compat
- stylelint-no-unsupported-browser-features
- postcss-normalize
- obsolete-webpack-plugin
浏览器查询过程
可以编写类似于这样的配置:
> 1%
last 2 versions
not dead
这些工具会根据我们的配置来获取相关的浏览器信息,以方便决定是否需要进行兼容性的支持,条件查询使用的是 caniuse-lite 的工具,这个工具的数据来自于 caniuse 的网站上。
Browserslist 编写规则
- defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
- 5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过。
- dead:24个月内没有官方支持或更新的浏览器。现在是IE 10,IE_Mob 11,BlackBerry 10,BlackBerry 7, Samsung 4和OperaMobile 12.1。
- last 2 versions:每个浏览器的最后2个版本。
- not ie <= 8:排除先前查询选择的浏览器。
命令行使用browserslist:npx browserslist ">1%, last 2 version, not dead"
配置browserslist - 在package.json中配置
"browserslist":[
"last 2 version",
"not dead",
"> 0.2%"
]
- .browserslistrc文件
> 0.5%
last 2 version
not dead
browserslist 包在安装 webpack 的时候自动下载,项目中有 browserslistrc 之后,执行 npx browserslist,可以看到符合当前规则的浏览器列表。
多个条件之间的关系
4.PostCSS
作用:CSS的转换和适配(自动添加浏览器前缀、css样式的重置)
1. 命令行使用postcss
安装:npm install postcss postcss-cli -D
案例:写一个添加前缀的 css
安装 autoprefixer npm install autoprefixer -D
使用postcss工具,并且制定使用autoprefixer:npx postcss --use autoprefixer -o end.css ./src/css/style.css
转换之后:
2. postcss-loader
安装npm install postcss-loader -D
配置(postcss需要有对应的插件):
5.文件加载
file-loader 处理文件资源
file-loader
可以处理 import/require()方式引入的一个文件资源,并且将其放到输出的文件夹中。
安装npm install file-loader -D
配置:
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: "img/[name].[hash:6].[ext]",
// 输出文件存放路径
outputPath: "img"
}
}
]
}
处理后的文件名称按照一定的规则进行显示:保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值。
常用的 placeholder
- [ext]: 处理文件的扩展名。
- [name]:处理文件的名称。
- [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制)
- [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到)
- [hash:]:截图hash的长度,默认32个字符太长了
- [path]:文件相对于webpack配置文件的路径
url-loader 处理小文件资源
url-loader 和 file-loader的工作方式相似,但是可以将较小的文件,转成 base64 的 URI
原则:小的图片转换base64(和页面一起请求,减少不必要的请求),大的图片要进行转换,反而会影响页面的请求速度
安装:npm install url-loader -D
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
name: "img/[name].[hash:6].[ext]",
// 小于 100 kb会转换为 uri,大于的不转换
limit: 100 * 1024,
outputPath:"img"
}
}
]
}
asset module type
webpack5 之前,加载资源需要使用一些loader,比如raw-loader 、url-loader、file-loader,但是在 webpack5之后可以直接使用资源模块类型(asset module type),来替代上面的这些loader。
asset module type 通过添加 4 种新的模块类型,来替换所有这些 loader\
- asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset/resource"
}
- asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
- asset/source 导出资源的源代码。之前通过使用 raw-loader 实现
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源 体积限制实现
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]" // 输出路径和文件名
},
parser: {
dataUrlCondition: {
maxSize: 100 * 1024
}
}
}
处理字体图标
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]",
}
}
5.Plugin
Loader 是用于特定的模块类型进行转换,Plugin 可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等。
CleanWebpackPlugin
作用:重新打包,自动删除 dist 文件夹
安装:npm install clean-webpack-plugin -D
配置:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
HtmlWebpackPlugin
作用:打包之后自动创建对应的入口文件的 index.html
安装npm install html-webpack-plugin -D
配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module: {
plugins: [
new HtmlWebpackPlugin({
// 填充语法<% 变量 %>
title: "webpack项目",
// 要使用的模块所在的路径
template: "./public/index.html"
})
]
}
DefinePlugin
可以处理模板中的 <link rel="icon" href="<%= BASE_URL %>favicon.ico">,定义 BASE_URL 常量值
const { DefinePlugin } = require('webpack');
module: {
plugins: [
new DefinePlugin({
BASE_URL: '"./"'
})
]
}
CopyWebpackPlugin
将一些文件放到public的目录
安装:npm install copy-webpack-plugin -D
- from :设置从哪一个源中开始复制
- to:复制到的位置,可以省略,会默认复制到打包的目录下
- globOptions:设置一些额外的选项,其中可以编写需要忽略的文件
6.webpack 对 common.js的支持
面试时看5
源代码:
工具包 ./js/format.js
const dateFormat = (date) => {
return "2020-12-12";
}
const priceFormat = (price) => {
return "100.00";
}
module.exports = {
dateFormat,
priceFormat
}
打包入口文件common_index.js
const { dateFormat, priceFormat } = require('./js/format');
console.log(dateFormat());
console.log(priceFormat());
打包配置文件 webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: "development",
entry: "./src/common_index.js",
devtool: "source-map",
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "./build"),
},
plugins: [
new HtmlWebpackPlugin({
title: "webpack commonjs"
})
]
}
打包之后的文件
// 定义了一个对象
// 模块的路径(key): 函数(value)
var __webpack_modules__ = {
"./src/js/format.js":
(function (module) {
const dateFormat = (date) => {
return "2020-12-12";
}
const priceFormat = (price) => {
return "100.00";
}
// 将我们要导出的变量, 放入到module对象中的exports对象
module.exports = {
dateFormat,
priceFormat
}
})
}
// 定义一个对象, 作为加载模块的缓存
var __webpack_module_cache__ = {};
// 是一个函数, 当我们加载一个模块时, 都会通过这个函数来加载
function __webpack_require__(moduleId) {
// 1.判断缓存中是否已经加载过
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// 2.给module变量和__webpack_module_cache__[moduleId]赋值了同一个对象
var module = __webpack_module_cache__[moduleId] = { exports: {} };
// 3.加载执行模块
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 4.导出module.exports {dateFormat: function, priceForamt: function}
return module.exports;
}
// 具体开始执行代码逻辑
!function () {
// 1.加载./src/js/format.js
const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");
console.log(dateFormat());
console.log(priceFormat());
}();
7.webpack 对 ESModule 的支持
工具包 ./js/format.js
export const sum = (num1, num2) => {
return num1 + num2;
}
export const mul = (num1, num2) => {
return num1 * num2;
}
打包入口文件 es_index.js
import { sum, mul } from "./js/math";
console.log(mul(20, 30));
console.log(sum(20, 30));
webpack 配置同上
打包之后的文件
// 1.定义了一个对象, 对象里面放的是我们的模块映射
var __webpack_modules__ = {
"./src/es_index.js":
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
// 调用r的目的是记录时一个__esModule -> true
__webpack_require__.r(__webpack_exports__);
// _js_math__WEBPACK_IMPORTED_MODULE_0__ == exports
var _js_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/math.js");
console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.mul(20, 30));
console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30));
}),
"./src/js/math.js":
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
// 调用了d函数: 给exports设置了一个代理definition
// exports对象中本身是没有对应的函数
__webpack_require__.d(__webpack_exports__, {
"sum": function () { return sum; },
"mul": function () { return mul; }
});
const sum = (num1, num2) => {
return num1 + num2;
}
const mul = (num1, num2) => {
return num1 * num2;
}
})
};
// 2.模块的缓存
var __webpack_module_cache__ = {};
// 3.require函数的实现(加载模块)
function __webpack_require__(moduleId) {
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
!function () {
// __webpack_require__这个函数对象添加了一个属性: d -> 值function
__webpack_require__.d = function (exports, definition) {
for (var key in definition) {
if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
}();
!function () {
// __webpack_require__这个函数对象添加了一个属性: o -> 值function
__webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();
!function () {
// __webpack_require__这个函数对象添加了一个属性: r -> 值function
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
}();
__webpack_require__("./src/es_index.js");
8.webpack CommonJS 和 ESModule 相互导入的支持
打包入口文件
// es module导出内容, CommonJS导入内容
const { sum, mul } = require("./js/math");
// CommonJS导出内容, es module导入内容
import { dateFormat, priceFormat } from "./js/format";
console.log(sum(20, 30));
console.log(mul(20, 30));
console.log(dateFormat("aaa"));
console.log(priceFormat("bbb"));
打包生成的文件
var __webpack_modules__ = ({
"./src/index.js":
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _js_format__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/format.js");
var _js_format__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_js_format__WEBPACK_IMPORTED_MODULE_0__);
// es module导出内容, CommonJS导入内容
const math = __webpack_require__("./src/js/math.js");
// CommonJS导出内容, es module导入内容
console.log(math.sum(20, 30));
console.log(math.mul(20, 30));
console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().dateFormat("aaa"));
console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().priceFormat("bbb"));
}),
"./src/js/format.js":
(function (module) {
const dateFormat = (date) => {
return "2020-12-12";
}
const priceFormat = (price) => {
return "100.00";
}
module.exports = {
dateFormat,
priceFormat
}
}),
"./src/js/math.js":
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
"sum": function () { return sum; },
"mul": function () { return mul; }
});
const sum = (num1, num2) => {
return num1 + num2;
}
const mul = (num1, num2) => {
return num1 * num2;
}
})
});
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
!function () {
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function () { return module['default']; } :
function () { return module; };
__webpack_require__.d(getter, { a: getter });
return getter;
};
}();
/* webpack/runtime/define property getters */
!function () {
// define getter functions for harmony exports
__webpack_require__.d = function (exports, definition) {
for (var key in definition) {
if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
}();
/* webpack/runtime/hasOwnProperty shorthand */
!function () {
__webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();
/* webpack/runtime/make namespace object */
!function () {
// define __esModule on exports
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
}();
__webpack_require__("./src/index.js");
9.source-map
作用:从已转换的代码,映射到原始的源文件。使浏览器可以重构原始源并在调试器中显示重建的原始源。
使用方式:
- 配置 source-map
module.exports = {
mode: "development",
devtool: "source-map"
}
- 在打包后的代码,最后添加一个注释,它指向 sourcemap
//# sourceMappingURL=common.bundle.js.map\ - 在Chrome中,可以按照如下的方式打开source-map
source-map 文件
{
"version": 3,
"sources": [
"webpack:./src/index.js",
"webpack:./src/js/format.js",
"webpack:./src/js/math.js",
"webpack:/webpack/bootstrap",
"webpack:k/webpack/runtime/compat get default export",
"webpack:/webpack/runtime/define property getters",
"webpack:/webpack/runtime/hasOwnProperty shorthand",
"webpack:/webpack/runtime/make namespace object",
"webpack:/webpack/startup"
],
"names": [
"require",
"sum",
"mul",
"console",
"log",
"dateFormat",
"priceFormat",
"abc",
"date",
"price",
"module",
"exports",
"num1",
"num2"
],
"mappings": ";;;;;;;;;;;;;AAAA;eACqBA,mBAAO,CAAC,mCAAD,C;IAApBC,G,YAAAA,G;IAAKC,G,YAAAA,G,EAEb;;;AACA;AAEAC,OAAO,CAACC,GAAR,CAAYH,GAAG,CAAC,EAAD,EAAK,EAAL,CAAf;AACAE,OAAO,CAACC,GAAR,CAAYF,GAAG,CAAC,EAAD,EAAK,EAAL,CAAf;AAEAC,OAAO,CAACC,GAAR,CAAYC,sDAAU,CAAC,KAAD,CAAtB;AACAF,OAAO,CAACC,GAAR,CAAYE,uDAAW,CAAC,KAAD,CAAvB;AAEAH,OAAO,CAACC,GAAR,CAAYG,GAAZ,E;;;;;;;;;;ACZA,IAAMF,UAAU,GAAG,SAAbA,UAAa,CAACG,IAAD,EAAU;AAC3B,SAAO,YAAP;AACD,CAFD;;AAIA,IAAMF,WAAW,GAAG,SAAdA,WAAc,CAACG,KAAD,EAAW;AAC7B,SAAO,QAAP;AACD,CAFD,C,CAIA;;;AAEAC,MAAM,CAACC,OAAP,GAAiB;AACfN,YAAU,EAAVA,UADe;AAEfC,aAAW,EAAXA;AAFe,CAAjB,C;;;;;;;;;;;;;;;;ACVO,IAAML,GAAG,GAAG,SAANA,GAAM,CAACW,IAAD,EAAOC,IAAP,EAAgB;AACjC,SAAOD,IAAI,GAAGC,IAAd;AACD,CAFM;AAIA,IAAMX,GAAG,GAAG,SAANA,GAAM,CAACU,IAAD,EAAOC,IAAP,EAAgB;AACjC,SAAOD,IAAI,GAAGC,IAAd;AACD,CAFM,C;;;;;;UCJP;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCrBA;WACA;WACA;WACA,cAAc,0BAA0B,EAAE;WAC1C,cAAc,eAAe;WAC7B,gCAAgC,YAAY;WAC5C;WACA,E;;;;;WCPA;WACA;WACA;WACA;WACA,wCAAwC,yCAAyC;WACjF;WACA;WACA,E;;;;;WCPA,6CAA6C,wDAAwD,E;;;;;WCArG;WACA;WACA;WACA,sDAAsD,kBAAkB;WACxE;WACA,+CAA+C,cAAc;WAC7D,E;;;;UCNA;UACA;UACA;UACA",
"file": "js/bundle.js",
"sourceRoot": ""
}
source-map 文件分析
- version:当前使用的版本,也就是最新的第三版
- sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件)
- names:转换前的变量和属性名称(因为我目前使用的是development模式,所以不需要保留转换前的名称
- mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriablelength quantity可变长度值)编码;
- file:打包后的文件(浏览器加载的文件);
- sourceContent:转换前的具体代码信息(和sources是对应的关系);
- sourceRoot:所有的sources相对的根目录;
生成 source-map 可选项
- 下面几个值不会生成 source-map 文件
- false:不使用source-map,也就是没有任何和source-map相关的内容。
- none:production模式下的默认值,不生成source-map。
- eval:development模式下的默认值,不生成source-map,但是它会在eval执行的代码中,添加
//# sourceURL=;被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码,如下:可以大概的看到源文件的报错位置
eval-source-map
会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 eval 函数的后面。inline-source-map
作用:生成 sourcemap,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面。cheap-source-map
作用:生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)cheap-module-source-map
生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好(例如 ts-loader将 ts 文件转换为 js 文件,babel会把箭头函数等转换为js)
cheap-source-map和cheap-module-source-map的区别?
hidden-source-map
生成 sourcemap,但是不会对 source-map 文件进行引用,相当于删除了打包文件中对sourcemap的引用注释
// 被删除掉的
//# sourceMappingURL=bundle.js.map
手动添加进来,那么 sourcemap 就会生效了。
nosources-source-map
生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;
点击错误提示,无法查看源码:
多个值的组合
webpack 提供给我们的26个值,是可以进行多组合的。
组合的规则如下:
- inline-|hidden-|eval:三个值时三选一
- nosources:可选值
- cheap可选值,并且可以跟随module的值
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
source-map 最佳实践 - 开发阶段:推荐使用 source-map 或者 cheap-module-source-map ,这分别是vue和react使用的值,可以获取调试信息,方便快速开发。
- 测试阶段:推荐使用 source-map或者cheap-module-source-map ü 测试阶段我们也希望在浏览器下看到正确的错误提示。
- 发布阶段:false、缺省值(不写)
10.Babel
作用:Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的 JavaScript,包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等。
Babel命令行使用
安装:npm install @babel/cli @babel/core
@babel/core:babel的核心代码,必须安装,@babel/cli:可以让我们在命令行使用babel
执行npx babel src --out-dir dist
例:转换箭头函数
使用 @babel/plugin-transform-arrow-functions 插件
安装:npm install @babel/plugin-transform-arrow-functions -D
使用npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
Babel的预设preset
@babel/preset-env 中包含了一些常用的预设 babel 插件,不用单独引入插件了。
安装npm install @babel/preset-env -D
执行npx babel src --out-dir dist --presets=@babel/preset-env
webpack 中配置 babel
安装npm install babel-loader @babel/core
单个使用插件的方式
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping"
]
}
}
}
]
},
使用 preset-env 预设
安装npm install @babel/preset-env
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env"]
]
}
}
}
]
}
babel 会按照 browserslist 的配置将源代码编译成 目标浏览器 可以识别的语法。也可以在 targets 中进行配置浏览器的范围
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env",{
target:"last 2 version"
}]
]
}
}
}
]
}
如果 browserslist 和 targets 都设置了,targets属性会覆盖browserslist。
11.polyfill
作用:项目中使用了一些语法特性(例如:Promise, Generator, Symbol等以及实例方法例如 Array.prototype.includes等),打包之后,某些浏览器压根不认识这些特性,必然会报错,使用 polyfill 来填充或者打补丁,那么就会包含该特性了。
使用方式:
- babel7.4.0之前,可以使用 @babel/polyfill的包,但是该包现在已经不推荐使用了
- babel7.4.0之后,可以通过单独引入core-js和regenerator-runtime来完成polyfill的使用:
安装npm install core-js regenerator-runtime --save
配置:
module.exports = {
presets: [
["@babel/preset-env", {
// false: 不用任何的polyfill相关的代码
// usage: 代码中需要哪些polyfill, 就引用相关的api
// entry: 手动在入口文件中导入 core-js/regenerator-runtime, 根据目标浏览器引入所有对应的polyfill
useBuiltIns: "usage",
corejs: 3
}],
["@babel/preset-react"]
]
}
- useBuiltIns:设置以什么样的方式来使用 polyfill; useBuiltIns属性有三个常见的值
- false 打包后的文件不使用polyfill来进行适配,不需要设置corejs属性的。
- usage 根据源代码中出现的语言特性,自动检测所需要的polyfill,确保最终包里的polyfill数量的最小化,打包的包相对会小一些
- entry 如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器 可能会报错,可以在入口文件中添加
import 'core-js/stable';
import 'regenerator-runtime/runtime';
这样做会根据 browserslist 目标导入所有的polyfill,但是对应的包也会变大。
- corejs:设置 corejs 的版本,目前使用较多的是3.x的版本,比如我使用的是3.8.x的版本,另外 corejs 可以设置是否对提议阶段的特性进行支持,设置 proposals 属性为 true 即可。
12.webpack 对 TypeScript的编译
- 使用ts-loader
ts-loader 的原理是用了 tsc 编译 ts 文件
安装:npm install ts-loader -D
配置:
{
test: /\.ts$/,
exclude: /node_modules/,
use:[
"ts-loader"
]
}
- 使用babel-loader
原理:babel 可以主动编译 ts 文件,不依赖 tsc
Babel是有对TypeScript进行支持,可以使用 ts@babel/preset-typescript
安装npm install @babel/preset-typescript -D
ts-loader和babel-loader选择
- 使用ts-loader(TypeScript Compiler),来直接编译TypeScript,那么只能将ts转换成js,如果我们还希望在这个过程中添加对应的polyfill,那么ts-loader是无能为力的,需要借助于babel来完成 polyfill 的填充功能。
- 使用babel-loader(Babel) 直接编译TypeScript,也可以将ts转换成js,并且可以实现 polyfill 的功能,但是babel-loader在编译的过程中,不会对类型错误进行检测。
编译TypeScript最佳实践
使用Babel来完成代码的转换,使用tsc来进行类型的检查。
package.json\
"scripts": {
"build": "webpack --config webpack.config.js",
"type-check": "tsc --noEmit",
"type-check-watch": "tsc --noEmit --watch"
}
npm run type-check可以对ts代码的类型进行检测,npm run type-check-watch可以实时的检测类型错误。
13.ESLint
ESLint是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析)
项目中使用 ESLint 建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性。
安装npm install eslint -D
创建ESLint的配置文件npx eslint --init
选择想要使用的ESLint:
执行检测命令
npx eslint ./src/main.js
ESLint的文件解析
module.exports = {
// env:运行的环境,比如是浏览器,并且我们会使用es2021(对应的ecmaVersion是12)的语法
env: {
browser: true,
commonjs: true,
es2021: true,
},
// extends:可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个)
extends: ['plugin:vue/essential', 'airbnb-base'],
// 指定ESMAScript的版本、sourceType的类型
parserOptions: {
ecmaVersion: 12,
// 编译TypeScript解释器
parser: '@typescript-eslint/parser',
},
// 用到的插件
plugins: ['vue', '@typescript-eslint'],
// 自定义规则
rules: {
// 0 => off
// 1 => warn
// 2 => error
'no-unused-vars': 0,
quotes: ['warn', 'single'],
'no-console': 0,
'import/no-extraneous-dependencies': 0,
},
};
VSCode的ESLint插件
如果每次校验时,都需要执行一次npm run eslint就有点麻烦了,所以我们可以使用一个VSCode的插件: ESLint
ESLint-Loader
编译代码的时候,也希望进行代码的eslint检测,就可以使用 eslint-loader 来完成
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader'],
},
{
test: /\.ts$/,
exclude: /node_modules/,
// 本质上是依赖于typescript(typescript compiler)
use: 'babel-loader',
}
]
}
14.本地服务器 devServer
自动编译有以下三种方式
1. Webpack watch
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译
开启 watch
在 package.json 的 scripts 中添加一个 watch 的脚本
2. webpack-dev-server
安装:npm install --save-dev webpack-dev-server
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中。
"scripts": {
"build": "webpack",
"watch": "webpack --watch",
"serve": "webpack serve"
},
15.模块热替换(HMR)
模块热替换:应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面。
HMR通过如下几种方式,来提高开发的速度
- 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
- 只更新需要变化的内容,节省开发的时间
- 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式
开启HMR
修改webpack的配置:
devServer: {
hot: true
}
浏览器可以看到如下效果:
修改了某一个模块的代码时,依然是刷新的整个页面:
需要去指定哪些模块发生更新时,进行HMR
if (module.hot) {
module.hot.accept("./math.js", () => {
console.log("math模块发生了更新~");
});
}
框架的HMR
1. React的HMR
在之前,React是借助于React Hot Loader来实现的HMR,目前已经改成使用react-refresh来实现了
安装:npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
修改webpack.config.js和babel.config.js文件
2.Vue的HMR
vue-loader 加载的组件默认进行 HMR 的处理
安装:npm install vue-loader vue-template-compiler -D
配置webpack.config.js:
3.HMR的原理
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket),express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析),HMR Socket Server,是一个socket的长连接,当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk),通过长连接,可以直接将这两个文件主动发送给客户端(浏览器),浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新。
16.webpack的路径处理
output 中的 path:静态资源js、css等输出目录,常见设置为dist、build文件夹等。\
output: {
filename: "bundle.js",
// 打包后的文件的输出目录
path: path.resolve(__dirname, "./build")
}
output 中的 publicPath:指定index.html文件打包引用的一个基本路径,默认值是一个空字符串,所以我们打包后引入js文件时,路径是 bundle.js。vue项目如果希望本地直接打开html文件来运行,会将其设置为 ./,路径时 ./bundle.js,可以根据相对路径去 查找资源。
output: {
filename: "bundle.js",
// 打包后的文件的输出目录
path: path.resolve(__dirname, "./build"),
publicPath: "./"
}
devServer 的 publicPath
devServer 中的 publicPath 属性,指定本地服务所在的文件夹:
默认值是 /,直接访问端口即可访问其中的资源 http://localhost:8080 如果设置为了 /abc,需要通过 http://localhost:8080/abc 才能访问到对应的打包后的资源。建议 devServer.publicPath 与 output.publicPath相同。\