Webpack从入门到进阶(一)---附沿路学习案例代码

Webpack从入门到进阶(一)—附沿路学习案例代码

一、Webpack简介

1、前端发展的几个阶段

无论是作为专业的开发者还是接触互联网的普通人,其实都能深刻的感知到Web前端的发展是非常快速的

  • 对于开发者来说我们会更加深有体会;

  • 从后端渲染的JSP、PHP,到前端原生JavaScript,再到jQuery开发,再到目前的三大框架Vue、React、Angular;

  • 开发方式也从原来的JavaScript的ES5语法,到ES6、7、8、9、10,到TypeScript,包括编写CSS的预处理器less、scss等;

在这里插入图片描述

前端开发的复杂化

前端开发目前我们面临哪些复杂的问题呢?

  • 比如开发过程中我们需要通过模块化的方式来开发;
  • 比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码;
  • 比如开发过程中,我们还希望试试的监听文件的变化来并且反映到浏览器上,提高开发的效率;
  • 比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化;
  • 等等
2、前端三个框架的脚手架

目前前端流行的三大框架:Vue、React、Angular

  • 但是事实上,这三大框架的创建过程我们都是借助于脚手架(CLI)的;
  • 事实上Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less、TypeScript、打包优化等的;

在这里插入图片描述

3、Webpack是什么?

webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;

webpack is a static module bundler for modern JavaScript applications.

我们来对上面的解释进行拆解:

  • 打包bundler:webpack可以将帮助我们进行打包,所以它是一个打包工具

  • 静态的static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);

  • 模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;

  • 现代的modern:我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;

Webpack 是一个前端资源加载和打包工具。所谓的模块就是在平时的前端开发中,用到一些静态资源,如JavaScript、CSS、图片等文件,webpack就将这些静态资源文件称之为模块。 webpack支持AMD和CommonJS,以及其他的一些模块系统,并且兼容多种JS书写规范,可以处理模块间的依赖关系,所以具有更强大的JS模块化的功能,它能对静态资源进行统一的管理以及打包发布。
作为一款 Grunt和Gulp的替代产品,Webpack受到大多数开发者的喜爱,因为它能够编译打包CSS,做CSS预处理,对JS的方言进行编译,打包图片,代码压缩等等。

与其他的构建工具相比,Webpack具有如下的一些优势:

  • 对 CommonJS 、 AMD 、ES6 的语法做了兼容;
  • 对 js、css、图片等资源文件都支持打包;
  • 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对 CoffeeScript、ES6的支持;
  • 有独立的配置文件 webpack.config.js;
  • 可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间;
  • 支持 SourceUrls 和 SourceMaps,易于调试;
  • 具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活;
  • webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快。

在Webpack的世界里有两个最核心的概念:

  1. 一切皆模块
    正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require(‘myJSfile.js’)亦可以require(‘myCSSfile.css’)。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。

  2. 按需加载
    传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。

Webpack官网图片

在这里插入图片描述

工作中的webpack

在这里插入图片描述

4、webpack和vite

我们来提一个问题:webpack会被vite取代吗?

  • vite推出后确实引起了很多的反响,也有很多人看好vite的发展(包括我);

但是目前vite取代webpack还有很长的路要走

  • 目前vue的项目支持使用vite,也支持使用webpack;
  • React、Angular的脚手架目前没有支持,暂时也没有转向vite的打算;
  • vite最终打包的过程,依然需要借助于rollup来完成;

vite的核心思想并不是首创

  • 事实上,vite的很多思想和之前的snowpack是重合的,而且相对目前来说snowpack会更加成熟;
  • 当然,后续发展来看vite可能会超越snowpack;

webpack的更新迭代

  • webpack在发展的过程中,也会不断改进自己,借鉴其他工具的一些优势和思想;
  • 在这么多年的发展中,无论是自身的优势还是生态都是非常强大的;

关于vite的思考

我的个人观点:

学习任何的东西,重要的是学习核心思想:

  • 我们不能学会了JavaScript,有一天学习TypeScript、Java或者其他语言,所有的内容都是从零开始的;
  • 我们在掌握了react之后,再去学习vue、Angular框架,也应该是可以快速上手的;

任何工具的出现,都是更好的服务于我们开发:

  • 无论是vite的出现,还是以后新的工具的出现,不要有任何排斥的思想

  • 我们要深刻的明白,工具都是为了更好的给我们提供服务;

  • 不可能出现了某个工具,让我们的开发效率变得更低,而这个工具却可以变得非常流行,这是不存在的;

5、vue-cli-service运行过程

在这里插入图片描述

6、Webpack的官方文档

webpack的官方文档是https://webpack.js.org/

  • webpack的中文官方文档是https://webpack.docschina.org/
  • DOCUMENTATION:文档详情,也是我们最关注的

点击DOCUMENTATION来到文档页:

  • API:API,提供相关的接口,可以自定义编译的过程(比如自定义loader和Plugin可以参考该位置的API)
  • BLOG:博客,等同于上一个tab的BLOG,里面有一些博客文章;
  • CONCEPTS:概念,主要是介绍一些webpack的核心概念,比如入口、出口、Loaders、Plugins等等,但是这里并没有一些对它们解析的详细API;
  • CONFIGURATION:配置,webpack详细的配置选项,都可以在这里查询到,更多的时候是作为查询手册;
  • GUIDES:指南,更像是webpack提供给我们的教程,我们可以按照这个教程一步步去学习webpack的使用过程;
  • LOADERS:loaders,webpack的核心之一,常见的loader都可以在这里查询到用法,比如css-loader、babel-loader、less-loader等等;
  • PLUGINS:plugins,webpack的核心之一,常见的plugin都可以在这里查询到用法,比如BannerPlugin、CleanWebpackPlugin、MiniCssExtractPlugin等等;
  • MIGRATE:迁移,可以通过这里的教程将webpack4迁移到webpack5等;
7、Webpack的依赖和安装

Webpack的运行是依赖Node环境的,所以我们电脑上必须有Node环境

  • 所以我们需要先安装Node.js,并且同时会安装npm;
  • Node官方网站:https://nodejs.org/

Webpack的安装

webpack的安装目前分为两个:webpack、webpack-cli

那么它们是什么关系呢?

  • 执行webpack命令,会执行node_modules下的.bin目录下的webpack;
  • webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
  • 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
  • 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)

在这里插入图片描述

npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装

传统开发存在的问题

我们的代码存在什么问题呢?某些语法浏览器是不认识的(尤其在低版本浏览器上)

  • 1.使用了ES6的语法,比如const、箭头函数等语法;
  • 2.使用了ES6中的模块化语法;
  • 3.使用CommonJS的模块化语法;
  • 4.在通过script标签引入时,必须添加上 type=“module” 属性;

显然,上面存在的问题,让我们在发布静态资源时,是不能直接发布的,因为运行在用户浏览器必然会存在各种各样的兼容性问题。

  • 我们需要通过某个工具对其进行打包,让其转换成浏览器可以直接识别的语法;
8、webpack默认打包

我们可以通过webpack进行打包,之后运行打包之后的代码

  • 在目录下直接执行 webpack 命令
webpack

生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:

  • 这个文件中的代码被压缩和丑化了;
  • 我们暂时不关心他是如何做到的,后续我讲webpack实现模块化原理时会再次讲到;
  • 另外我们发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续我们需要通过babel来进行转换和设置;

我们发现是可以正常进行打包的,但是有一个问题,webpack是如何确定我们的入口的呢?

  • 事实上,当我们运行webpack时,webpack会查找当前目录下的 src/index.js作为入口;
  • 所以,如果当前项目中没有存在src/index.js文件,那么会报错;
9、Webpack配置文件

在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不可以的

我们可以在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:

const path = require('path');

module.exports = {
	entry: "./src/main.js",
	output: {
		filename: "bundle.js",
		path: path.resolve(__dirname, "./dist")
	}
}

继续执行webpack命令,依然可以正常打包

指定配置文件

但是如果我们的配置文件并不是webpack.config.js的名字,而是其他的名字呢?

  • 比如我们将webpack.config.js修改成了 wk.config.js;

  • 这个时候我们可以通过 --config 来指定对应的配置文件;

webpack --config wk.config.js

但是每次这样执行命令来对源码进行编译,会非常繁琐,所以我们可以在package.json中增加一个新的脚本:

{
	"scripts": {
		"build": webpack --config wk.config.js
	}
}

之后我们执行 npm run build来打包即可。

10、Webpack依赖图

webpack到底是如何对我们的项目进行打包的呢?

  • 事实上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件;

  • 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等);

  • 然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析);

在这里插入图片描述

二、Webpack知识点

一、Webpack配置和css处理
1、webpack打包和配置

webpack默认打包

我们可以通过webpack进行打包,之后运行打包之后的代码

  • 在目录下直接执行 webpack 命令
  • webpack

生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:

  • 这个文件中的代码被压缩和丑化了;

  • 我们暂时不关心他是如何做到的,后续我讲webpack实现模块化原理时会再次讲到;

  • 另外我们发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续我们需要通过babel来进行转换和设置;

我们发现是可以正常进行打包的,但是有一个问题,webpack是如何确定我们的入口的呢?

  • 事实上,当我们运行webpack时,webpack会查找当前目录下的 src/index.js作为入口;

  • 所以,如果当前项目中没有存在src/index.js文件,那么会报错;

当然,我们也可以通过配置来指定入口和出口

npx webpack --entry ./src/main.js --output-path ./build

Webpack配置文件

在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不可以的

我们可以在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:

const path = require('path');

module.exports = {
	entry: "./src/main.js",
	output: {
		filename: "bundle.js",
		path: path.resolve(__dirname, "./dist")
	}
}

继续执行webpack命令,依然可以正常打包

在这里插入图片描述

output里面的path 不能是相对路径,否则会报错

指定配置文件

但是如果我们的配置文件并不是webpack.config.js的名字,而是其他的名字呢?

  • 比如我们将webpack.config.js修改成了 wk.config.js;

  • 这个时候我们可以通过 --config 来指定对应的配置文件;

webpack --config wk.config.js

但是每次这样执行命令来对源码进行编译,会非常繁琐,所以我们可以在package.json中增加一个新的脚本:

  "scripts": {
    "build": "webpack --config wk.config.js"
  },

之后我们执行 npm run build来打包即可。

Webpack依赖图

webpack到底是如何对我们的项目进行打包的呢?

  • 事实上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件;

  • 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等);

  • 然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析);

在这里插入图片描述

注:没有被引入的模块、资源,不会被打包(npm install 的node_modules下的资源也一样)

编写案例代码

我们创建一个component.js

  • 通过JavaScript创建了一个元素,并且希望给它设置一些样式;
// component.js
import "../css/index.css";
import "../css/component.less";

function component() {
  const element = document.createElement("div");

  element.innerHTML = ["Hello", "Webpack"].join(" ");
  element.className = "content";

  return element;
}

document.body.appendChild(component());
// index.css
@import "./test.css";

.demo {
  color: red;
}

继续编译命令npm run build

在这里插入图片描述

2、CSS处理

css-loader

css-loader的使用

上面的错误信息告诉我们需要一个loader来加载这个css文件,但是loader是什么呢?

  • loader 可以用于对模块的源代码进行转换;

  • 我们可以将css文件也看成是一个模块,我们是通过import来加载这个模块的;

  • 在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能;

那么我们需要一个什么样的loader呢?

  • 对于加载css文件来说,我们需要一个可以读取css文件的loader;

  • 这个loader最常用的是css-loader;

css-loader的安装:

npm install css-loader -D

css-loader的使用方案

如何使用这个loader来加载css文件呢?有三种方式:

  • 内联方式;
  • CLI方式(webpack5中不再使用);
  • 配置方式;

**内联方式:**内联方式使用较少,因为不方便管理;

  • 在引入的样式前加上使用的loader,并且使用!分割;
import "css-loader!../css/style.css"

CLI方式

  • 在webpack5的文档中已经没有了–module-bind;

  • 实际应用中也比较少使用,因为不方便管理;

loader配置方式

配置方式表示的意思是在我们的webpack.config.js文件中写明配置信息:

  • module.rules中允许我们配置多个loader(因为我们也会继续使用其他的loader,来完成其他文件的加载);

  • 这种方式可以更好的表示loader的配置,也方便后期的维护,同时也让你对各个Loader有一个全局的概览;

module.rules的配置如下:

rules属性对应的值是一个数组:[Rule]

数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:

  • test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;

  • use属性:对应的值时一个数组:[UseEntry]

    • UseEntry是一个对象,可以通过对象的属性来设置一些其他属性
      • loader:必须有一个 loader属性,对应的值是一个字符串;
      • options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;
      • query:目前已经使用options来替代;
    • 传递字符串(如:use: [ ‘style-loader’ ])是 loader 属性的简写方式(如:use: [ { loader: ‘style-loader’} ]);
  • loader属性: Rule.use: [ { loader } ] 的简写。

Loader的配置代码

const path = require('path');

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, "./build")
  },
  module: {
    rules: [
      {
        // 规则使用正则表达式
        test: /\.css$/, // 匹配资源
        use: [
          // { loader: "css-loader" },
          // 注意: 编写顺序(从下往上, 从右往做, 从后往前)
          "style-loader", 
          {
            loader: "css-loader",
            options: {
              importLoaders: 1
            }
          },
          "postcss-loader"
        ],
        // loader: "css-loader"
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "postcss-loader",
          "less-loader"
        ]
      }
    ]
  }
}

style-loader

我们已经可以通过css-loader来加载css文件了

  • 但是你会发现这个css在我们的代码中并没有生效(页面没有效果)。

这是为什么呢?

  • 因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中;

  • 如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader;

安装style-loader:

npm install style-loader -D

配置style-loader

那么我们应该如何使用style-loader:

  • 在配置文件中,添加style-loader;

  • 注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将style-loader写到css-loader的前面;

use: [
	// 注意: style-loader在css-loader之前
	{ loader: "style-loader" },
	{ loader: "css-loader"}
]

重新执行编译npm run build,可以发现打包后的css已经生效了:

  • 当前目前我们的css是通过页内样式的方式添加进来的;

  • 后续我们也会讲如何将css抽取到单独的文件中,并且进行压缩等操作;

less-loader

如何处理less文件?

在我们开发中,我们可能会使用less、sass、stylus的预处理器来编写css样式,效率会更高。

那么,如何可以让我们的环境支持这些预处理器呢?

  • 首先我们需要确定,less、sass等编写的css需要通过工具转换成普通的css;

比如我们编写如下的less样式:

@fontSize: 50px;
@fontWeight: 700;

.content {
  font-size: @fontSize;
  font-weight: @fontWeight;
}

Less工具处理

我们可以使用less工具来完成它的编译转换:

npm install less -D

执行如下命令:

npx less ./src/css/title.less > title.css

less-loader处理

但是在项目中我们会编写大量的css,它们如何可以自动转换呢?

  • 这个时候我们就可以使用less-loader,来自动使用less工具转换less到css;
  • less-loader需要less工具进行less文件的处理
npm install less less-loader -D

配置webpack.config.js

{
  test: /\.less$/,
  use: [
  	{ loader: 'style-loader' },
    { loader: 'css-loader' },
    { loader: 'less-loader' },
  ]
}

执行npm run build

less就可以自动转换成css,并且页面也会生效了

3、浏览器兼容性

我们来思考一个问题:开发中,浏览器的兼容性问题,我们应该如何去解决和处理?

  • 当然这个问题很笼统,这里我说的兼容性问题不是指屏幕大小的变化适配;

  • 我这里指的兼容性是针对不同的浏览器支持的特性:比如css特性、js语法,之间的兼容性;

我们知道市面上有大量的浏览器:

  • 有Chrome、Safari、IE、Edge、Chrome for Android、UC Browser、QQ Browser等等;

  • 它们的市场占率是多少?我们要不要兼容它们呢?

其实在很多的脚手架配置中,都能看到类似于这样的配置信息:

  • 这里的百分之一,就是指市场占有率
> 1%
last 2 versions
not dead

浏览器市场占有率

但是在哪里可以查询到浏览器的市场占有率呢?

  • 这个最好用的网站,也是我们工具通常会查询的一个网站就是caniuse;

  • https://caniuse.com/usage-table

在这里插入图片描述

browserslist

认识browserslist工具

但是有一个问题,我们如何可以在css兼容性和js兼容性下共享我们配置的兼容性条件呢?

  • 就是当我们设置了一个条件: > 1%;

  • 我们表达的意思是css要兼容市场占有率大于1%的浏览器,js也要兼容市场占有率大于1%的浏览器;

  • 如果我们是通过工具来达到这种兼容性的,比如后面我们会讲到的postcss-prest-env、babel、autoprefixer等

如何可以让他们共享我们的配置呢?

  • 这个问题的答案就是Browserslist

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%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过。

  • 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浏览器。

  • last 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:排除先前查询选择的浏览器。

命令行使用browserslist

我们可以直接通过命令来查询某些条件所匹配到的浏览器:

npx browserslist ">1%, last 2 version, not dead"

在这里插入图片描述

配置browserslist

我们如何可以配置browserslist呢?两种方案:

  • 方案一:在package.json中配置;

  • 方案二:单独的一个配置文件.browserslistrc文件;

方案一:package.json配置:

"browsweslist": [
	"last 2 version",
	"not dead",
	"> 0.2%"
]

方案二:.browserslistrc文件

>1%
last 2 version
not dead

默认配置和条件关系

如果没有配置,那么也会有一个默认配置:

// Default browers query
browserslist.defaults = [
	'> 0.5%',
	'last 2 versions',
	'Firefox ESR',
	'not dead'
]

我们编写了多个条件之后,多个条件之间是什么关系呢?

在这里插入图片描述

4、PostCSS

认识PostCSS工具

什么是PostCSS呢?

  • PostCSS是一个通过JavaScript来转换样式的工具

  • 这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;

  • 但是实现这些工具,我们需要借助于PostCSS对应的插件;

如何使用PostCSS呢?主要就是两个步骤:

  • 第一步:查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;

  • 第二步:选择可以添加你需要的PostCSS相关的插件;

命令行使用postcss

当然,我们能不能也直接在终端使用PostCSS呢?

  • 也是可以的,但是我们需要单独安装一个工具postcss-cli;

我们可以安装一下它们:postcss、postcss-cli

npm install postcss postcss-cli -D 

我们编写一个需要添加前缀的css:

  • https://autoprefixer.github.io/

  • 我们可以在上面的网站中查询一些添加css属性的样式;

:fullscreen {
	color: red;
}
.content {
  user-select: none;
}

插件autoprefixer

因为我们需要添加前缀,所以要安装autoprefixer:

npm install autoprefixer -D

直接使用使用postcss工具,并且制定使用autoprefixer

npx postcss --use autoprefixer -o end.css ./src/css/style.css

转化之后的css样式如下:

:ms-fullscreen {

}
:fullscreen {
}
.content {
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

单独的postcss配置文件

当然,我们也可以将这些配置信息放到一个单独的文件中进行管理:

  • 在根目录下创建postcss.config.js
module.exports = {
  plugins: [
    'postcss-preset-env'
  ]
}

postcss-preset-env

事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer。

我们可以使用另外一个插件:postcss-preset-env

  • postcss-preset-env也是一个postcss的插件;

  • 它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill;

  • 也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);

首先,我们需要安装postcss-preset-env:

npm install postcss-preset-env -D

之后,我们直接修改掉之前的autoprefixer即可:

plugins: [
	require("postcss-preset-env")
]

注意:我们在使用某些postcss插件时,也可以直接传入字符串

module.exports = {
  plugins: [
    'postcss-preset-env'
  ]
}

举个例子

我们举一个例子:

  • 我们这里在使用十六进制的颜色时设置了8位;

  • 但是某些浏览器可能不认识这种语法,我们最好可以转成RGBA的形式;

  • 但是autoprefixer是不会帮助我们转换的;

  • 而postcss-preset-env就可以完成这样的功能;

.content {
	color: $12345678;
}
二、Webpack加载和处理其他资源
1、案例准备

为了演示我们项目中可以加载图片,我们需要在项目中使用图片,比较常见的使用图片的方式是两种:

  • img元素,设置src属性;

  • 其他元素(比如div),设置background-image的css属性;

// import "css-loader!../css/index.css";
import "../css/index.css";
import "../css/component.less";

import zznhImage from "../img/zznh.png";

function component() {
  const element = document.createElement("div");

  element.innerHTML = ["Hello", "Webpack"].join(" ");
  element.className = "content";

  // 创建一个img元素,设置src属性
  const imgEl = new Image();
  // imgEl.src = require("../img/zznh.png").default;
  imgEl.src = zznhImage;
  element.appendChild(imgEl);

  // 创建一个div, 设置背景图片
  const bgDivEl = document.createElement('div');
  bgDivEl.style.width = 200 + 'px';
  bgDivEl.style.height = 200 + 'px';
  bgDivEl.className = 'bg-image';
  bgDivEl.style.backgroundColor = "red";
  element.appendChild(bgDivEl);

  // 创建一个i元素, 设置一个字体
  const iEl = document.createElement("i");
  iEl.className = "iconfont icon-ashbin why_icon";
  element.appendChild(iEl);

  return element;
}

document.body.appendChild(component());
@import "./test.css";
@import "../font/iconfont.css";

.demo {
  color: red;
}

.bg-image {
  display: inline-block;
  background-image: url('../img/nhlt.jpg');
  background-size: contain;
}

.why_icon {
  display: inline-block;
  font-size: 50px;
  color: red;
}

这个时候,打包会报错

2、file-loader

要处理jpg、png等格式的图片,我们也需要有对应的loader:file-loader

  • file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;

  • 当然我们待会儿可以学习如何修改它的名字和所在文件夹;

安装file-loader:

npm install file-loader -D

配置处理图片的Rule:

{
	test: /\.(png|jpe?g|gif|svg)$/,
	use: {
		loader: "file-loader"
	}
}

在这里插入图片描述

文件的名称规则

有时候我们处理后的文件名称按照一定的规则进行显示:

  • 比如保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值等;

这个时候我们可以使用PlaceHolders来完成,webpack给我们提供了大量的PlaceHolders来显示不同的内容:

  • https://webpack.js.org/loaders/file-loader/#placeholders

  • 我们可以在文档中查阅自己需要的placeholder;

我们这里介绍几个最常用的placeholder:

  • [ext]: 处理文件的扩展名;

  • **[name]:**处理文件的名称;

  • **[hash]:**文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);

  • **[contentHash]:**在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);

  • **[hash:<length>]:**截图hash的长度,默认32个字符太长了;

  • **[path]:**文件相对于webpack配置文件的路径;

设置文件名称

那么我们可以按照如下的格式编写:

  • 这个也是vue的写法;
{
	test: /\.(png|jpe?g|gif|svg)$/,
	use: {
		loader: "file-loader",
		options: {
			name: "img/[name].[hash:8].[ext]"
		}
	}
}

设置文件的存放路径

当然,我们刚才通过 img/ 已经设置了文件夹,这个也是vue、react脚手架中常见的设置方式:

  • 其实按照这种设置方式就可以了;

  • 当然我们也可以通过outputPath来设置输出的文件夹;

{
	test: /\.(png|jpe?g|gif|svg)$/,
	use: {
		loader: "file-loader",
		options: {
			name: "img/[name].[hash:8].[ext]",
			outputPath: "img"
		}
	}
}
3、url-loader

url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。

安装url-loader:

npm install url-loader -D
{
	test: /\.(png|jpe?g|gif|svg)$/,
	use: {
		loader: "url-loader",
		options: {
			name: "img/[name].[hash:8].[ext]",
			outputPath: "img"
		}
	}
}

显示结果是一样的,并且图片可以正常显示;

但是在dist文件夹中,我们会看不到图片文件:

  • 这是因为我的两张图片的大小分别是38kb和295kb;
  • 默认情况下url-loader会将所有的图片文件转成base64编码

url-loader的limit

但是开发中我们往往是小的图片需要转换,但是大的图片直接使用图片即可

  • 这是因为小的图片转换base64之后可以和页面一起被请求,减少不必要的请求过程;

  • 而大的图片也进行转换,反而会影响页面的请求速度;

那么,我们如何可以限制哪些大小的图片转换和不转换呢?

  • url-loader有一个options属性limit,可以用于设置转换的限制;

  • 下面的代码38kb的图片会进行base64编码,而295kb的不会;

{
	test: /\.(png|jpe?g|gif|svg)$/,
	use: {
		loader: "url-loader",
		options: {
			limit: 100 * 1024,
			name: "img/[name].[hash:8].[ext]",
			outputPath: "img"
		}
	}
}
4、asset module type

asset module type的介绍

我们当前使用的webpack版本是webpack5:

  • 在webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader;

  • 在webpack5之后,我们可以直接使用资源模块类型(asset module type),来替代上面的这些loader;

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;

  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;

  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;

  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现;

Asset module type的使用

比如加载图片,我们可以使用下面的方式:

{
	test: /\.(png|jpe?g|gif|svg)$/,
	type: "asset/resource"
}

但是,如何可以自定义文件的输出路径和文件名呢?

  • **方式一:**修改output,添加assetModuleFilename属性;

  • **方式二:**在Rule中,添加一个generator属性,并且设置filename;

output: {
    filename: "bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, "./build"),
    assetModuleFilename: "img/[name].[hash:6][ext]"
  },
{
  test: /\.(png|jpe?g|gif|svg)$/,
  // type: "asset/resource", file-loader的效果
  // type: "asset/inline", url-loader
  type: "asset",
  generator: {
    filename: "img/[name].[hash:6][ext]"
  },
},

url-loader的limit效果

我们需要两个步骤来实现:

  • **步骤一:**将type修改为asset;

  • **步骤二:**添加一个parser属性,并且制定dataUrl的条件,添加maxSize属性;

{
  test: /\.(png|jpe?g|gif|svg)$/,
  // type: "asset/resource", file-loader的效果
  // type: "asset/inline", url-loader
  type: "asset",
  generator: {
    filename: "img/[name].[hash:6][ext]"
  },
  parser: {
    dataUrlCondition: {
      maxSize: 100 * 1024
    }
  }
},

加载字体文件

如果我们需要使用某些特殊的字体或者字体图标,那么我们会引入很多字体相关的文件,这些文件的处理也是一样的。

首先,我从阿里图标库中下载了几个字体图标:

在这里插入图片描述

在component中引入,并且添加一个i元素用于显示字体图标:

// 创建一个i元素, 设置一个字体
  const iEl = document.createElement("i");
  iEl.className = "iconfont icon-ashbin why_icon";
  element.appendChild(iEl);

字体的打包

这个时候打包会报错,因为无法正确的处理eot、ttf、woff等文件:

  • 我们可以选择使用file-loader来处理,也可以选择直接使用webpack5的资源模块类型来处理;
{
  test: /\.ttf|eot|woff2?$/i,
  type: "asset/resource",
  generator: {
    filename: "font/[name].[hash:6][ext]"
  }
}
4、Plugin

认识Plugin

Webpack的另一个核心是Plugin,官方有这样一段对Plugin的描述:

  • While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.

上面表达的含义翻译过来就是:

  • Loader是用于特定的模块类型进行转换;

  • Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;

在这里插入图片描述

CleanWebpackPlugin

前面我们演示的过程中,每次修改了一些配置,重新打包时,都需要手动删除dist文件夹:

  • 我们可以借助于一个插件来帮助我们完成,这个插件就是CleanWebpackPlugin;

首先,我们先安装这个插件:

npm install clean-webpack-plugin -D

之后在插件中配置:

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

module.exports = {
	plugins: [
    	new CleanWebpackPlugin(),
    ]
}

HtmlWebpackPlugin

另外还有一个不太规范的地方:

  • 我们的HTML文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的。

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

  • 所以我们也需要对index.html进行打包处理;

对HTML进行打包处理我们可以使用另外一个插件:HtmlWebpackPlugin

npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
	plugins: [
    	new HtmlWebpackPlugin({
          title: "coderwhy webpack",
        }),
    ]
}

生成的index.html分析

我们会发现,现在自动在dist文件夹中,生成了一个index.html的文件:

  • 该文件中也自动添加了我们打包的bundle.js文件;
<!doctype html>
<html lang="">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <link rel="icon" href="./favicon.ico">
  <title>coderwhy webpack</title>
</head>

<body>	
  <script src="bundle.js"></script>
</body>

</html>

这个文件是如何生成的呢?

  • 默认情况下是根据ejs的一个模板来生成的;

  • 在html-webpack-plugin的源码中,有一个default_index.ejs模块;

自定义HTML模板

如果我们想在自己的模块中加入一些比较特别的内容:

  • 比如添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示;

  • 比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签 <div id="app"></div>

这个我们需要一个属于自己的index.html模块:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

自定义模板数据填充

上面的代码中,会有一些类似这样的语法<% 变量 %>,这个是EJS模块填充数据的方式。

在配置HtmlWebpackPlugin时,我们可以添加如下配置:

  • **template:**指定我们要使用的模块所在的路径;

  • **title:**在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
	plugins: [
    	new HtmlWebpackPlugin({
          title: "coderwhy webpack",
          template: "./public/index.html"
        }),
    ]
}

DefinePlugin的介绍

但是,这个时候编译还是会报错,因为在我们的模块中还使用到一个BASE_URL的常量:

在这里插入图片描述

这是因为在编译template模块时,有一个BASE_URL:

  • <link rel="icon" href="<%= BASE_URL %>favicon.ico">

  • 但是我们并没有设置过这个常量值,所以会出现没有定义的错误;

这个时候我们可以使用DefinePlugin插件;

DefinePlugin的使用

DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):

const { DefinePlugin } = require('webpack');
module.exports = {
	plugins: [
		new DefinePlugin({
      		BASE_URL: '"./"'
    	}),
	]
}

这个时候,编译template就可以正确的编译了,会读取到BASE_URL的值;

CopyWebpackPlugin

在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。

  • 这个复制的功能,我们可以使用CopyWebpackPlugin来完成;

安装CopyWebpackPlugin插件:

npm install copy-webpack-plugin -D

接下来配置CopyWebpackPlugin即可:

  • 复制的规则在patterns中设置;

  • **from:**设置从哪一个源中开始复制;

  • **to:**复制到的位置,可以省略,会默认复制到打包的目录下;

  • **globOptions:**设置一些额外的选项,其中可以编写需要忽略的文件:

    • .DS_Store:mac目录下回自动生成的一个文件;
    • index.html:也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;
new CopyWebpackPlugin({
      patterns: [
        {
          from: "public",
          globOptions: {
            ignore: [
              "**/index.html",
              "**/.DS_Store",
              "**/abc.txt"
            ]
          }
        }
      ]
    })
三、模块化原理和source-map
1、Mode

Mode配置

前面我们一直没有讲mode,但是在这里我们要简单讲一下,后面还会提到它的其他用法。

Mode配置选项,可以告知webpack使用响应模式的内置优化:

  • 默认值是production(什么都不设置的情况下);

  • 可选值有:‘none’ | ‘development’ | ‘production’;

这几个选项有什么样的区别呢?

在这里插入图片描述

Mode配置代表更多

在这里插入图片描述

2、Webpack的模块化

Webpack打包的代码,允许我们使用各种各样的模块化,但是最常用的是CommonJS、ES Module

  • 那么它是如何帮助我们实现了代码中支持模块化呢?

我们来研究一下它的原理,包括如下原理:

  • CommonJS模块化实现原理;

  • ES Module实现原理;

  • CommonJS加载ES Module的原理;

  • ES Module加载CommonJS的原理;

// 01_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("abc"));
  console.log(priceFormat("abc"));
}();
// 02_ESModule的实现原理
// 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");
// 03_CommonJS和ESModule相互导入
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");
3、source-map

认识source-map

我们的代码通常运行在浏览器上时,是通过打包压缩的:

  • 也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的;

  • 比如ES6的代码可能被转换成ES5;

  • 比如对应的代码行号、列号在经过编译后肯定会不一致;

  • 比如代码进行丑化压缩时,会将编码名称等修改;

  • 比如我们使用了TypeScript等方式编写的代码,最终转换成JavaScript;

但是,当代码报错需要调试时(debug),调试转换后的代码是很困难的

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

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

  • source-map是从已转换的代码,映射到原始的源文件;

  • 使浏览器可以重构原始源并在调试器中显示重建的原始源;

如何使用source-map

如何可以使用source-map呢?两个步骤:

  • 第一步:根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map;

  • 第二步:在转换后的代码,最后添加一个注释,它指向sourcemap;

//# sourceMappingURL=common.bundle.js.map

浏览器会根据我们的注释,查找响应的source-map,并且根据source-map还原我们的代码,方便进行调试。

在Chrome中,我们可以按照如下的方式打开source-map:

在这里插入图片描述

分析source-map

最初source-map生成的文件带下是原始文件的10倍,第二版减少了约50%,第三版又减少了50%,所以目前一个133kb的文件,最终的source-map的大小大概在300kb。

目前的source-map长什么样子呢?

  • version:当前使用的版本,也就是最新的第三版;

  • sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);

  • names:转换前的变量和属性名称(因为我目前使用的是development模式,所以不需要保留转换前的名称);

  • mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable-length quantity可变长度值)编码;

  • file:打包后的文件(浏览器加载的文件);

  • sourceContent:转换前的具体代码信息(和sources是对应的关系);

  • sourceRoot:所有的sources相对的根目录;

source-map文件

参考文档(MDN):https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/SourceMap.jsm

在这里插入图片描述

生成source-map

如何在使用webpack打包的时候,生成对应的source-map呢?

  • webpack为我们提供了非常多的选项(目前是26个),来处理source-map;

  • https://webpack.docschina.org/configuration/devtool/

  • 选择不同的值,生成的source-map会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择;

下面几个值不会生成source-map

**false:**不使用source-map,也就是没有任何和source-map相关的内容。

**none:**production模式下的默认值,不生成source-map。

**eval:**development模式下的默认值,不生成source-map

  • 但是它会在eval执行的代码中,添加 //# sourceURL=;

  • 它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码;

eval的效果

在这里插入图片描述

source-map值

source-map

  • 生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件;

bundle文件中有如下的注释:

  • 开发工具会根据这个注释找到source-map文件,并且解析;
//# sourceMappingURL=bundle.js.map

在这里插入图片描述

eval-source-map值

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

在这里插入图片描述

inline-source-map值

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

在这里插入图片描述

cheap-source-map

cheap-source-map

  • 会生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)

  • 因为在开发中,我们只需要行信息通常就可以定位到错误了

在这里插入图片描述

cheap-module-source-map值

cheap-module-source-map

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

**这里有一个很模糊的概念:**对源自loader的sourcemap处理会更好,官方也没有给出很好的解释

  • 其实是如果loader对我们的源码进行了特殊的处理,比如babel;

如果我这里使用了babel-loader(注意:目前还没有讲babel)

  • 可以先按照我的babel配置演练;
module: {
  rules: [
    {
      test: /\.js$/,
      use: {
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"]
        }
      }
    }
  ]
},

cheap-source-map和cheap-module-source-map

cheap-source-mapcheap-module-source-map的区别:

在这里插入图片描述

hidden-source-map值

hidden-source-map

  • 会生成sourcemap,但是不会对source-map文件进行引用;

  • 相当于删除了打包文件中对sourcemap的引用注释;

// 被删除掉的
//# sourceMappingURL=bundle.js.map

如果我们手动添加进来,那么sourcemap就会生效了

nosources-source-map值

nosources-source-map

  • 会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;

正确的错误提示:

在这里插入图片描述

点击错误提示,无法查看源码:
在这里插入图片描述

多个值的组合

事实上,webpack提供给我们的26个值,是可以进行多组合的。

组合的规则如下:

  • **inline-|hidden-|eval:**三个值时三选一;

  • **nosources:**可选值;

  • cheap可选值,并且可以跟随module的值;

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

那么在开发中,最佳的实践是什么呢?

  • 开发阶段:推荐使用 source-map或者cheap-module-source-map
    • 这分别是vue和react使用的值,可以获取调试信息,方便快速开发;
  • 测试阶段:推荐使用 source-map或者cheap-module-source-map
    • 测试阶段我们也希望在浏览器下看到正确的错误提示;
  • 发布阶段:false、缺省值(不写)

三、知识扩展

1、谷歌浏览器设置跨域

在这里插入图片描述

在进行前端开发设置谷歌浏览器跨域时遇到了问题

总结三种方法:

一、49版本以前的设置:
 在桌面chrome快捷方式的属性中的目标输入框添加 --disable-web-security 添加部分与前面字符之间有空格(有文章说目标引号结尾的加 --args --disable-web-security,反正我试过,没有49版本之前的)

在这里插入图片描述

二、49版本以后的设置:
1.在电脑上新建一个目录,例如:C:\MyChromeDevUserData

2.在属性页面中的目标输入框里加上 --disable-web-security --user-data-dir=C:\MyChromeDevUserData,–user-data-dir的值就是刚才新建的目录(参考上面截图)
(我用此方法能成功设置)

三、如果以上两种方法失败,用以下方法(此方法为网上copy)

​ 1.通过桌面快捷方式打开文件位置,找到谷歌浏览器安装位置,看到有个chrome.exe文件

2.在电脑上新建一个目录,例如:C:\MyChromeDevUserData

3.打开cmd命令行,进入快捷方式位置 例如

 cd C:\Program Files (x86)\Google\Chrome\Application

4.通过命令行启动谷歌浏览器

 C:\Program Files (x86)\Google\Chrome\Application>chrome.exe --disable-web-security --user-data-dir=C:\MyChromeDevUserData

各位根据自己情况做相应更改即可

在这里插入图片描述

2、使用vscode模块化开发加载js类型为module所出现的问题<script type=‘module‘>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script src="./src/index.js" type="module"></script>
</body>
</html>

通过默认浏览器运行时报错,加载js被阻止。出现了以下错误

在这里插入图片描述

解决方法如下:

  1. 下载安装 live server

在这里插入图片描述

  1. 选择需要运行的文件,通过open with live server打开

在这里插入图片描述

  1. 运行成功

在这里插入图片描述

3、Module not found: Error: Can’t resolve ‘.\src\main.js’ in XXXX

在这里插入图片描述

配置webpack打包主入口和出口

npx webpack --entry .\src\main.js --output-path .\build // 报上面的错误

错误原因:

无法解析Windows的.\src\main.js路径。需要换成./src/main.js

也就是,改成下面这样就不会报这个错了

npx webpack --entry ./src/main.js --output-path ./build

在这里插入图片描述

4、assetModuleType 方式打包 require的图片加载不出来
{
  test: /\.(png|jpg|jpe?g|gif|svg)$/,
  // type: "asset/resource", // file-loader效果
  // type: "asset/inline", // url-loader
  type: "asset",
  generator: {
    filename: "img/[name].[hash:6][ext]"
  },
  parser: {
    dataUrlCondition: {
      maxSize: 100 * 1024
    }
  }
},
const imgEl = new Image();
  imgEl.src = require('../img/zznh.png').default
  element.appendChild(imgEl)

通过以上方式打包之后的,加载不出zznh.png这张图片

如果换成下面这种引入方式,则可以加载出来

import zznhImage from "../img/zznh.png";

const imgEl = new Image();
  // imgEl.src = require('../img/zznh.png').default
  imgEl.src = zznhImage
  element.appendChild(imgEl)

或者不用assetModuleType。重新使用file-loader那种方式

{
  test: /\.(png|jpg|jpe?g|gif|svg)$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        name: "img/[name].[hash:6].[ext]",
        // outputPath: 'img'
      }
    }
  ]
}

这样子打包也可以正常引入zznh这张图片

5、npx postcss unexpected identifier

在这里插入图片描述

一开始,我以为是因为淘宝镜像引起的问题,因为一开始我通过npm安装依赖报错了,就直接用cnpm 安装依赖了

结果npm安装依赖之后的报错更加简便,只有一个

Unexpected identifier

百度也找不到答案,一开始我以为是postcss-cli的版本问题。我一开始的版本是9.1.0,这个时候执行上面那条命令,就只会爆出这个错误。后面我就重新安装postcss-cli

npm uninstall postcss-cli
npm install postcss-cli@8.3.1

之后再执行上述命令行语句之后,这时候的报错才显得正常了起来

ExperimentalWarning: The fs.promises API is experimental

原来是node版本过旧,我原先的node版本为10.16.0,这个版本太老了,重新安装node到14.17.6就一切都正常了

6、CopyWebpackPlugin出现HookWebpackError: Not supported
[webpack-cli] HookWebpackError: Not supported
    at xxxxxxxxxxx\node_modules\copy-webpack-plugin\dist\index.js:485:13
    .....
-- inner error --

在这里插入图片描述

版本问题导致

CopyWebpackPlugin报错版本:10.0.0

解决办法:降低版本

npm i -D copy-webpack-plugin@9.*

四、沿途案例学习代码-码云地址

码云地址

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值