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

35 篇文章 476 订阅
17 篇文章 19 订阅

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

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知识点

七、DLL
1、DLL认识、打包和使用

认识DLL库

DLL是什么呢?

  • DLL全程是动态链接库(Dynamic Link Library),是为软件在Windows中实现共享函数库的一种实现方式;

  • 那么webpack中也有内置DLL的功能,它指的是我们可以将可以共享,并且不经常改变的代码,抽取成一个共

享的库;

  • 这个库在之后编译的过程中,会被引入到其他项目的代码中;

DDL库的使用分为两步:

  • 第一步:打包一个DLL库;

  • 第二步:项目中引入DLL库

注意:在升级到webpack4之后,React和Vue脚手架都移除了DLL库(下面的vue作者的回复)

在这里插入图片描述

打包一个DLL库

如何打包一个DLLPlugin?(创建一个新的项目)

  • webpack帮助我们内置了一个DllPlugin可以帮助我们打包一个DLL的库文件;
module.exports = {
  entry: {
    react: ["react", "react-dom"]
  },
  output: {
    path: path.resolve(__dirname, "./dll"),
    filename: "dll_[name].js",
    library: 'dll_[name]'
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  },
  plugins: [
    new webpack.DllPlugin({
      name: "dll_[name]",
      path: path.resolve(__dirname, "./dll/[name].manifest.json")
    })
  ]
}

使用打包的DLL库

如果我们在我们的代码中使用了react、react-dom,我们有配置splitChunks的情况下,他们会进行分包,打包到一个独立的chunk中。

  • 但是现在我们有了dll_react,不再需要单独去打包它们,可以直接去引用dll_react即可:

  • 第一步:通过DllReferencePlugin插件告知要使用的DLL库;

  • 第二步:通过AddAssetHtmlPlugin插件,将我们打包的DLL库引入到Html模块中;

new webpack.DllReferencePlugin({
  context: resolveApp("./"),
  manifest: resolveApp("./dll/react.manifest.json")
}),
new AddAssetHtmlPlugin({
  filepath: resolveApp('./dll/dll_react.js')
})
2、Terser

Terser介绍和安装

什么是Terser呢?

  • Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集;

  • 早期我们会使用 uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;

  • Terser是从 uglify-es fork 过来的,并且保留它原来的大部分API以及适配 uglify-es和uglify-js@3等;

也就是说,Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。

因为Terser是一个独立的工具,所以它可以单独安装:

# 全局安装
npm install terser -g
# 局部安装
npm install terser

命令行使用Terser

我们可以在命令行中使用Terser:

terser [input files] [options]
# 举例说明
terser js/file1.js -o foo.min.js -c -m

我们这里来讲解几个Compress option和Mangle option:

  • 因为他们的配置非常多,我们不可能一个个解析,更多的查看文档即可;

  • https://github.com/terser/terser#compress-options

  • https://github.com/terser/terser#mangle-options

Compress和Mangle的options

Compress option:

  • arrows:class或者object中的函数,转换成箭头函数;

  • arguments:将函数中使用 arguments[index]转成对应的形参名称;

  • dead_code:移除不可达的代码(tree shaking);

  • 其他属性可以查看文档;

Mangle option

  • toplevel:默认值是false,顶层作用域中的变量名称,进行丑化(转换);

  • keep_classnames:默认值是false,是否保持依赖的类名称;

  • keep_fnames:默认值是false,是否保持原来的函数名称;

  • 其他属性可以查看文档;

npx terser ./src/abc.js -o abc.min.js -c 
arrows,arguments=true,dead_code -m 
toplevel=true,keep_classnames=true,keep_fnames=true

Terser在webpack中配置

真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:

  • 在webpack中有一个minimizer属性,在production模式下,默认就是使用TerserPlugin来处理我们的代码的;

  • 如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置;

首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)

其次,我们可以在minimizer创建一个TerserPlugin:

  • extractComments:默认值为true,表示会将注释抽取到一个单独的文件中;

    在开发中,我们不希望保留这个注释时,可以设置为false;

  • parallel:使用多进程并发运行提高构建的速度,默认值是true,并发运行的默认数量: os.cpus().length - 1;

    我们也可以设置自己的个数,但是使用默认值即可;

  • terserOptions:设置我们的terser相关的配置

    compress:设置压缩相关的选项;

    mangle:设置丑化相关的选项,可以直接设置为true;

    toplevel:底层变量是否进行转换;

    keep_classnames:保留类的名称;

    keep_fnames:保留函数的名称;

CSS的压缩

另一个代码的压缩是CSS:

  • CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;

  • CSS的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin;

  • css-minimizer-webpack-plugin是使用cssnano工具来优化、压缩CSS(也可以单独使用);

第一步,安装 css-minimizer-webpack-plugin:

npm install css-minimizer-webpack-plugin -D

第二步,在optimization.minimizer中配置

minimizer: [
	new TerserPlugin({....}),
	new CssMinimizerPlugin({
		parallel: true
	})
]
3、Tree Shaking

什么是Tree Shaking呢?

  • Tree Shaking是一个术语,在计算机中表示消除死代码(dead_code);

  • 最早的想法起源于LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式编程时,尽量使用纯函数的原因之一);

  • 后来Tree Shaking也被应用于其他的语言,比如JavaScript、Dart;

JavaScript的Tree Shaking:

  • 对JavaScript进行Tree Shaking是源自打包工具rollup(后面我们也会讲的构建工具);

  • 这是因为Tree Shaking依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系);

  • webpack2正式内置支持了ES2015模块,和检测未使用模块的能力;

  • 在webpack4正式扩展了这个能力,并且通过 package.json的 sideEffects属性作为标记,告知webpack在编译时,哪里文件可以安全的删除掉;

  • webpack5中,也提供了对部分CommonJS的tree shaking的支持;

    https://github.com/webpack/changelog-v5#commonjs-tree-shaking

webpack实现Tree Shaking

事实上webpack实现Tree Shaking采用了两种不同的方案:

  • usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的;

  • sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用;

usedExports

将mode设置为development模式:

  • 为了可以看到 usedExports带来的效果,我们需要设置为 development 模式

  • 因为在 production 模式下,webpack默认的一些优化会带来很大额影响。

设置usedExports为true和false对比打包后的代码:

  • 在usedExports设置为true时,会有一段注释:unused harmony export mul;

  • 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;

这个时候,我们讲 minimize设置true:

  • usedExports设置为false时,mul函数没有被移除掉;

  • usedExports设置为true时,mul函数有被移除掉;

所以,usedExports实现Tree Shaking是结合Terser来完成的。

sideEffects

sideEffects用于告知webpack compiler哪些模块时有副作用的:

  • 副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义;

  • 副作用的问题,在讲React的纯函数时是有讲过的;

在package.json中设置sideEffects的值:

  • 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;

  • 如果有一些我们希望保留,可以设置为数组;

比如我们有一个format.js、style.css文件:

  • 该文件在导入时没有使用任何的变量来接受;

  • 那么打包后的文件,不会保留format.js、style.css相关的任何代码;

"sideEffects": [
	"./src/util/format.js",
	"*.css"
]

Webpack中tree shaking的设置

所以,如何在项目中对JavaScript的代码进行TreeShaking呢(生成环境)?

  • 在optimization中配置usedExports为true,来帮助Terser进行优化;

  • 在package.json中配置sideEffects,直接对模块进行优化;

CSS实现Tree Shaking

上面我们学习的都是关于JavaScript的Tree Shaking,那么CSS是否也可以进行Tree Shaking操作呢?

  • CSS的Tree Shaking需要借助于一些其他的插件;

  • 在早期的时候,我们会使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不再维护了(最新更新也是在4年前了);

  • 目前我们可以使用另外一个库来完成CSS的Tree Shaking:PurgeCSS,也是一个帮助我们删除未使用的CSS的工具;

安装PurgeCss的webpack插件:

npm install purgecss-webpack-plugin -D

配置PurgeCss

配置这个插件(生成环境):

  • paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;

  • 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;

new PurgeCssPlugin({
  paths: glob.sync(`${resolveApp("./src")}/**/*`, {nodir: true}),
  safelist: function() {
    return {
      standard: ["body", "html"]
    }
  }
})

purgecss也可以对less文件进行处理(所以它是对打包后的css进行tree shaking操作);

Scope Hoisting

什么是Scope Hoisting呢?

  • Scope Hoisting从webpack3开始增加的一个新功能;

  • 功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;

默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的)IIFE:

  • 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数;

  • Scope Hoisting可以将函数合并到一个模块中来运行;

使用Scope Hoisting非常的简单,webpack已经内置了对应的模块:

  • 在production模式下,默认这个模块就会启用;

  • 在development模式下,我们需要自己来打开该模块;

new webpack.optimize.ModuleConcatenationPlugin()
4、HTTP压缩

HTTP压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式;

HTTP压缩的流程什么呢?

  • 第一步:HTTP数据在服务器发送前就已经被压缩了;(可以在webpack中完成)

  • 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式;

在这里插入图片描述

第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;

在这里插入图片描述

目前的压缩格式

目前的压缩格式非常的多:

  • compress – UNIX的“compress”程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate);

  • deflate – 基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装;

  • gzip – GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法;

  • br – 一种新的开源压缩算法,专为HTTP内容的编码而设计;

Webpack对文件压缩

webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin。

第一步,安装CompressionPlugin:

npm install compression-webpack-plugin -D

第二步,使用CompressionPlugin即可:

new CompressionPlugin({
  test: /\.(css|js)$/i,
  threshold: 0,
  minRatio: 0.8,
  algorithm: "gzip",
  // exclude
  // include
}),

HTML文件中代码的压缩

我们之前使用了HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置:

inject:设置打包的资源插入的位置

  • true、 false 、body、head

cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)

minify:默认会使用一个插件html-minifier-terser

在这里插入图片描述

InlineChunkHtmlPlugin

另外有一个插件,可以辅助将一些chunk出来的模块,内联到html中:

  • 比如runtime的代码,代码量不大,但是是必须加载的;

  • 那么我们可以直接内联到html中;

这个插件是在react-dev-utils中实现的,所以我们可以安装一下它:

npm install react-dev-utils -D

在production的plugins中进行配置:

new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/,])

封装Library

webpack可以帮助我们打包自己的库文件,比如我们需要打包一个coderwhy_utils的一个库。

// lib/format.js
export function dateFormat() {
  return "2021-11-11";
}

// lib/math.js
export function sum(num1, num2) {
  return num1 + num2;
}

export function mul(num1, num2) {
  return num1 + num2;
}

// index.js
import * as math from './lib/math';
import * as format from './lib/format';

console.log("abc");

export {
  math,
  format
}

打包Library

配置webpack.config.js文件

const path = require('path');

module.exports = {
  mode: "production",
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "coderwhy_utils.js",
    // AMD/CommonJS/浏览器
    // CommnJoS: 社区规范的CommonJS, 这个里面是没有module对象
    // CommonJS2: Node实现的CommonJS, 这个里面是有module对象, module.exports
    libraryTarget: "umd",
    library: "coderwhyUtils",
    globalObject: "self"
  }
}
!(function (e, t) {
  "object" == typeof exports && "object" == typeof module
    ? (module.exports = t())
    : "function" == typeof define && define.amd
    ? define([], t)
    : "object" == typeof exports
    ? (exports.coderwhyUtils = t())
    : (e.coderwhyUtils = t());
})(this, function () {})
八、打包分析和webpack源码
1、打包分析

分析一:打包的时间分析

如果我们希望看到每一个loader、每一个Plugin消耗的打包时间,可以借助于一个插件:speed-measure-webpack-plugin

  • 注意:该插件在最新的webpack版本中存在一些兼容性的问题(和部分Plugin不兼容)

  • 截止2021-3-10日,但是目前该插件还在维护,所以可以等待后续是否更新;

  • 我这里暂时的做法是把不兼容的插件先删除掉,也就是不兼容的插件不显示它的打包时间就可以了;

第一步,安装speed-measure-webpack-plugin插件

npm install speed-measure-webpack-plugin -D

第二步,使用speed-measure-webpack-plugin插件

  • 创建插件导出的对象 SpeedMeasurePlugin;

  • 使用 smp.wrap 包裹我们导出的webpack配置;

// 测量打包时间的插件
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

const webpackConfig = smp.wrap({
	plugins: [new Myplugin(), new MyOtherPlugin()]
})

分析二:打包后文件分析

方案一:生成一个stats.json的文件

"buiebpack ld:stats": "w--config ./config/webpack.common.js --env production --profile --json=stats.json",

通过执行npm run build:status可以获取到一个stats.json的文件:

  • 这个文件我们自己分析不容易看到其中的信息;

  • 可以放到 http://webpack.github.com/analyse,进行分析

在这里插入图片描述

方案二:使用webpack-bundle-analyzer工具

  • 另一个非常直观查看包大小的工具是webpack-bundle-analyzer。

第一步,我们可以直接安装这个工具:

npm install webpack-bundle-analyzer -D

第二步,我们可以在webpack配置中使用该插件:

// 打包大小分析的插件
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

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

在打包webpack的时候,这个工具是帮助我们打开一个8888端口上的服务,我们可以直接的看到每个包的大小。

  • 比如有一个包时通过一个Vue组件打包的,但是非常的大,那么我们可以考虑是否可以拆分出多个组件,并且对其进行懒加载;

  • 比如一个图片或者字体文件特别大,是否可以对其进行压缩或者其他的优化处理;

2、webpack源码

Webpack的启动流程

在这里插入图片描述

Webpack源码阅读

第一步:下载webpack的源码

  • https://github.com/webpack/webpack

第二步:安装项目相关的依赖

  • npm install

第三步:编写自己的源代码

  • 这里我创建了一个 why 文件夹,里面存放了一些代码

第四步:编写webpack的配置文件

  • webpack.config.js

第五步:编写启动的文件build.js

创建Compiler

在这里插入图片描述

Compiler中run方法执行的Hook

在这里插入图片描述

Compilation对Module的处理

在这里插入图片描述

module的build阶段

在这里插入图片描述

输出asset阶段

在这里插入图片描述

Compiler和Compilation的区别

Compiler和Compilation的区别

在webpack构建的之初就会创建的一个对象, 并且在webpack的整个生命周期都会存在(before - run - beforeCompiler - compile -make - finishMake - afterCompiler - done)

只要是做webpack的编译, 都会先创建一个Compiler

Compilation是到准备编译模块(比如main.js), 才会创建Compilation对象

主要是存在于 compile - make 阶段主要使用的对象

watch -> 源代码发生改变就需要重新编译模块

Compiler可以继续使用(如果我修改webpack的配置, 那么需要重新执行run run build)

Compilation需要创建一个新的Compilation对象

九、自定义Loader
1、创建自己的Loader

Loader是用于对模块的源代码进行转换(处理),之前我们已经使用过很多Loader,比如css-loader、style-loader、babel-loader等。

这里我们来学习如何自定义自己的Loader:

  • Loader本质上是一个导出为函数的JavaScript模块;

  • loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去;

编写一个hy-loader01.js模块这个函数会接收三个参数:

  • content:资源文件的内容;

  • map:sourcemap相关的数据;

  • meta:一些元数据;

module.exports = function (content, map, meta) {
	console.log(content);
	return contentl
}

在加载某个模块时,引入loader

注意:传入的路径和context是有关系的,在前面我们讲入口的相对路径时有讲过。

module.exports = {
  mode: "development",
  devtool: "source-map",
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "./build")
  },
  module: {
    rules: [
      // Rule对象
      {
        test: /\.js$/i,
        use: {
          loader: "hy-loader01",
          options: {
            name: "why",
            age: "18"
          }
        }
      },
    ]
  },
  resolveLoader: {
    modules: ["node_modules", "./hy-loaders"]
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

resolveLoader属性

但是,如果我们依然希望可以直接去加载自己的loader文件夹,有没有更加简洁的办法呢?

  • 配置resolveLoader属性;
resolveLoader: {
  modules: ["node_modules", "./hy-loaders"]
},

loader的执行顺序

创建多个Loader使用,它的执行顺序是什么呢?

  • 从后向前、从右向左的

在这里插入图片描述

pitch-loader和enforce

module.exports.pitch = function() {
	console.log("loader01 pitch")
}

执行顺序和enforce

其实这也是为什么loader的执行顺序是相反的:

  • run-loader先优先执行PitchLoader,在执行PitchLoader时进行loaderIndex++;

  • run-loader之后会执行NormalLoader,在执行NormalLoader时进行loaderIndex–;

那么,能不能改变它们的执行顺序呢?

  • 我们可以拆分成多个Rule对象,通过enforce来改变它们的顺序;

enforce一共有四种方式:

  • 默认所有的loader都是normal;
  • 在行内设置的loader是inline(在前面将css加载时讲过,import ‘loader1!loader2!./test.js’);
  • 也可以通过enforce设置 pre 和 post;

PitchingNormal它们的执行顺序分别是:

  • post, inline, normal, pre;

  • pre, normal, inline, post;

同步的Loader

什么是同步的Loader呢?

  • 默认创建的Loader就是同步的Loader;

  • 这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理;

  • 通常在有错误的情况下,我们会使用 this.callback;

this.callback的用法如下:

  • 第一个参数必须是 Error 或者 null;

  • 第二个参数是一个 string或者Buffer;

module.exports = function(content) {
	console.log("loader01", content);
	this.callback(null, content)
}

异步的Loader

什么是异步的Loader呢?

  • 有时候我们使用Loader时会进行一些异步的操作;

  • 我们希望在异步操作完成后,再返回这个loader处理的结果;

  • 这个时候我们就要使用异步的Loader了;

loader-runner已经在执行loader时给我们提供了方法,让loader变成一个异步的loader:

module.exports = function(content) {
	const callback = this.async()
	
	setTimeout(() => {
		console.log("loader01", content)
		callback(null, content)
	}, 1000)
}

传入和获取参数

在使用loader时,传入参数。

我们可以通过一个webpack官方提供的一个解析库 loader-utils,安装对应的库。

npm install loader-utils -D
{
  test: /\.js$/i,
  use: {
    loader: "hy-loader01",
    options: {
      name: "why",
      age: "18"
    }
  }
},
const babel = require("@babel/core");
const { getOptions } = require("loader-utils");

module.exports = function(content) {
  // 0.设置为异步的loader
  const callback = this.async();

  // 1.获取传入的参数
  const options = getOptions(this);

  // 2.对源代码进行转换
  babel.transform(content, options, (err, result) => {
    if (err) {
      callback(err);
    } else {
      callback(null, result.code)
    }
  })
}

校验参数

我们可以通过一个webpack官方提供的校验库 schema-utils,安装对应的库:

npm install schema-utils -D
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "请输入您的名字"
    },
    "age": {
      "type": "number",
      "description": "请输入您的年龄"
    }
  }, 
  "additionalProperties": true
}
const { getOptions } = require("loader-utils");
const { validate } = require('schema-utils');

const schema = require("../hy-schema/loader01-schema.json");

// NormalLoader
// 异步Loader: this.async()
module.exports = function(content) {
  console.log(content, "哈哈哈, 这是我的loader01");

  // 获取传入的参数:
  const options = getOptions(this);
  console.log("传入的参数是:", options);

  validate(schema, options, {
    name: "hy-loader02"
  })

  const callback = this.async();

  setTimeout(() => {
    callback(null, content);
  }, 2000);
}

babel-loader案例

我们知道babel-loader可以帮助我们对JavaScript的代码进行转换,这里我们定义一个自己的babel-loader:

const babel = require("@babel/core");
const { getOptions } = require("loader-utils");

module.exports = function(content) {
  // 0.设置为异步的loader
  const callback = this.async();

  // 1.获取传入的参数
  const options = getOptions(this);

  // 2.对源代码进行转换
  babel.transform(content, options, (err, result) => {
    if (err) {
      callback(err);
    } else {
      callback(null, result.code)
    }
  })
}
{
	"type": "object",
	"properties": {
		"presets": {
			"type": "array"
		}
	},
	"additinalProperties": true
}

hymd-loader

const marked = require('marked');
const hljs = require('highlight.js');

module.exports = function(content) {
  marked.setOptions({
    highlight: function(code, lang) {
      return hljs.highlight(lang, code).value;
    }
  })

  const htmlContent = marked(content);
  const innerContent = "`" + htmlContent + "`";
  const moduleCode = `var code=${innerContent}; export default code;`
  return moduleCode;
} 
十、自定义Plugin
1、自定义Plugin

我们知道webpack有两个非常重要的类:Compiler和Compilation

  • 他们通过注入插件的方式,来监听webpack的所有生命周期;

  • 插件的注入离不开各种各样的Hook,而他们的Hook是如何得到的呢?

  • 其实是创建了Tapable库中的各种Hook的实例;

所以,如果我们想要学习自定义插件,最好先了解一个库:Tapable

  • Tapable是官方编写和维护的一个库;

  • Tapable是管理着需要的Hook,这些Hook可以被应用到我们的插件中;

Tapable有哪些Hook呢?

在这里插入图片描述

Tapable的Hook分类

同步和异步的:

  • 以sync开头的,是同步的Hook;

  • 以async开头的,两个事件处理回调,不会等待上一次处理回调结束后再执行下一次回调;

其他的类别

  • bail:当有返回值时,就不会执行后续的事件触发了;

  • Loop:当返回值为true,就会反复执行该事件,当返回值为undefined或者不返回内容,就退出事件;

  • Waterfall:当返回值不为undefined时,会将这次返回的结果作为下次事件的第一个参数;

  • Parallel:并行,会同时执行次事件处理回调结束,才执行下一次事件处理回调;

  • Series:串行,会等待上一是异步的Hook;

Hook的使用过程

第一步:创建Hook对象

this.hooks = {
	syncHook: new SyncHook(["name", "age"])
}

第二步:注册Hook中的事件

this.hooks.syncHook.tap("event1", (name, age) => {
  console.log("event1", name, age);
  return "event1";
});

this.hooks.syncHook.tap("event2", (name, age) => {
  console.log("event2", name, age);
});

第三步:触发事件

emit() {
	this.hooks.syncHook.call("why", 18);
}

自定义Plugin

在之前的学习中,我们已经使用了非常多的Plugin:

  • CleanWebpackPlugin

  • HTMLWebpackPlugin

  • MiniCSSExtractPlugin

  • CompressionPlugin

  • 等等。。。

这些Plugin是如何被注册到webpack的生命周期中的呢?

  • 第一:在webpack函数的createCompiler方法中,注册了所有的插件;

  • 第二:在注册插件时,会调用插件函数或者插件对象的apply方法;

  • 第三:插件方法会接收compiler对象,我们可以通过compiler对象来注册Hook的事件;

  • 第四:某些插件也会传入一个compilation的对象,我们也可以监听compilation的Hook事件;

开发自己的插件

如何开发自己的插件呢?

  • 目前大部分插件都可以在社区中找到,但是推荐尽量使用在维护,并且经过社区验证的;

  • 这里我们开发一个自己的简单插件:将静态文件自动上传服务器中;

自定义插件的过程:

  • 创建AutoUploadWebpackPlugin类;

  • 编写apply方法:

    通过ssh连接服务器;

    删除服务器原来的文件夹;

    上传文件夹中的内容;

  • 在webpack的plugins中,使用AutoUploadWebpackPlugin类;

十一、Gulp工具
1、Vue脚手架

Vue脚手架启动

在这里插入图片描述

Vue脚手架加载配置

在这里插入图片描述

vue inspect --mode=development > dev.config.js
vue inspect --mode=production > prod.config.js
2、Gulp

什么是Gulp?

什么是Gulp?

  • A toolkit to automate & enhance your workflow;

  • 一个工具包,可以帮你自动化和增加你的工作流;

在这里插入图片描述

Gulp和Webpack

gulp的核心理念是task runner

  • 可以定义自己的一系列任务,等待任务被执行;

  • 基于文件Stream的构建流;

  • 我们可以使用gulp的插件体系来完成某些任务;

webpack的核心理念是module bundler

  • webpack是一个模块化的打包工具;

  • 可以使用各种各样的loader来加载不同的模块;

  • 可以使用各种各样的插件在webpack打包的生命周期完成其他的任务;

gulp相对于webpack的优缺点:

  • gulp相对于webpack思想更加的简单、易用,更适合编写一些自动化的任务;

  • 但是目前对于大型项目(Vue、React、Angular)并不会使用gulp来构建,比如默认gulp是不支持模块化的;

Gulp的基本使用

首先,我们需要安装gulp:

# 全局安装
npm install gulp -g
# 局部安装
npm install gulp

其次,编写gulpfile.js文件,在其中创建一个任务:

exports.foo = function() {
	console.log("foo task working");
	return src("./src/js/*.js")
}

最后,执行gulp命令:

npx gulp foo

创建gulp任务

每个gulp任务都是一个异步的JavaScript函数:

  • 此函数可以接受一个callback作为参数,调用callback函数那么任务会结束;

  • 或者是一个返回stream、promise、event emitter、child process或observable类型的函数;

任务可以是public或者private类型的:

  • 公开任务(Public tasks) 从 gulpfile 中被导出(export),可以通过 gulp 命令直接调用;

  • 私有任务(Private tasks) 被设计为在内部使用,通常作为 series() 或 parallel() 组合的组成部分;

补充:gulp4之前, 注册任务时通过gulp.task的方式进行注册的

gulp.task("bar", cb => {
	consoele.log("bar任务");
	cb();
})

默认任务

我们可以编写一个默认任务:

// 默认任务
exports.default = function(cb) {
	console.log("这是一个默认任务")
	cb();
}

执行 gulp 命令:

npx gulp

任务组合series和parallel

gulp提供了两个强大的组合方法:

  • series():串行任务组合;

  • parallel():并行任务组合;

  • 他们都可以接受任意数量的任务函数或者已经组合的操作;

const seriesTask = series(task1, task2, task3);
const parallelTask = parallel(task1, task2, task3);

const composeTask = series(parallelTask, seriesTask);

读取和写入文件

gulp 暴露了 src() 和 dest() 方法用于处理计算机上存放的文件。

  • src() 接受参数,并从文件系统中读取文件然后生成一个Node流(Stream),它将所有匹配的文件读取到内 存中并通过流(Stream)进行处理;

  • 由 src() 产生的流(stream)应当从任务(task函数)中返回并发出异步完成的信号;

  • dest() 接受一个输出目录作为参数,并且它还会产生一个 Node流(stream),通过该流将内容输出到文件中;

exports.default = function() {
	return src("./src/index.html").pipe(dest('output/'))
}

流(stream)所提供的主要的 API 是 .pipe() 方法,pipe方法的原理是什么呢

  • pipe方法接受一个 转换流(Transform streams)或可写流(Writable streams);

  • 那么转换流或者可写流,拿到数据之后可以对数据进行处理,再次传递给下一个转换流或者可写流;

对文件进行转换

如果在这个过程中,我们希望对文件进行某些处理,可以使用社区给我们提供的插件。

  • 比如我们希望ES6转换成ES5,那么可以使用babel插件;

  • 如果我们希望对代码进行压缩和丑化,那么可以使用uglify或者terser插件;

const jsTask = () => {
  return src("./src/js/**.js", {base: "./src"})
    .pipe(babel({presets: ["@babel/preset-env"]}))
    .pipe(terser({mangle: {toplevel: true}}))
    .pipe(dest("./dist"))
}

glob文件匹配

src() 方法接受一个 glob 字符串或由多个 glob 字符串组成的数组作为参数,用于确定哪些文件需要被操作。

  • glob 或 glob 数组必须至少匹配到一个匹配项,否则 src() 将报错;

glob的匹配规则如下:

  • (一个星号*):在一个字符串中,匹配任意数量的字符,包括零个匹配;
'*.js'
  • (两个星号**):在多个字符串匹配中匹配任意数量的字符串,通常用在匹配目录下的文件;
'scripts/**/*.js'
  • (取反!):

    由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的;

    所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面;

    第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分;

['scripts/**/*.js', '!scripts/vendor/']

Gulp的文件监听

gulp api 中的 watch() 方法利用文件系统的监控程序(file system watcher)将 与进行关联。

const jsTask = () => {
  // 从src中读取文件, 输出到dist文件夹中
  return (
    src("./src/**/*.js")
      .pipe(babel({ presets: ["@babel/preset-env"] }))
      // .pipe(uglify())
      .pipe(terser({ mangle: { toplevel: true } }))
      .pipe(dest("./dist"))
  );
};

watch("./src/**/*.js", jsTask);
3、Gulp案例

接下来,我们编写一个案例,通过gulp来开启本地服务和打包:

打包html文件;

打包JavaScript文件;

打包less文件;

Html资源注入

删除生成目录

创建打包任务

创建开发任务

const { src, dest, watch, series, parallel } = require('gulp');

const htmlMin = require('gulp-htmlmin');
const babel = require('gulp-babel');
const terser = require('gulp-terser');
const less = require('gulp-less'); // less
const postcss = require('gulp-postcss'); // postcss
const postcssPresetEnv = require('postcss-preset-env');
const inject = require('gulp-inject');

const browserSync = require('browser-sync');

const del = require('del');

const htmlTask = () => {
  return src("./src/*.html", {base: "./src"})
    .pipe(htmlMin({
      collapseWhitespace: true
    }))
    .pipe(dest("./dist"))
}

const jsTask = () => {
  return src("./src/js/**.js", {base: "./src"})
    .pipe(babel({presets: ["@babel/preset-env"]}))
    .pipe(terser({mangle: {toplevel: true}}))
    .pipe(dest("./dist"))
}

const lessTask = () => {
  return src("./src/css/*.less", {base: "./src"})
    .pipe(less())
    .pipe(postcss([postcssPresetEnv()]))
    .pipe(dest("./dist"))
}

const injectHtml = () => {
  return src("./dist/*.html")
    .pipe(inject(src(["./dist/js/*.js", "./dist/css/*.css"]), {relative: true}))
    .pipe(dest('./dist'))
}

// 搭建本地服务器
const bs = browserSync.create();
const serve = () => {
  watch("./src/*.html", series(htmlTask, injectHtml));
  watch("./src/js/*.js", series(jsTask, injectHtml));
  watch("./src/css/*.less", series(jsTask, lessTask));

  bs.init({
    port: 8080,
    open: true,
    files: "./dist/*",
    server: {
      baseDir: "./dist"
    }
  })
}

const clean = () => {
  return del(["dist"])
}

const buildTask = series(clean, parallel(htmlTask, jsTask, lessTask), injectHtml);
const serveTask = series(buildTask, serve);

module.exports = {
  serveTask,
  buildTask
}
十一、rollup工具
1、rollup

认识rollup

我们来看一下官方对rollup的定义:

  • Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application.

  • Rollup是一个JavaScript的模块化打包工具,可以帮助我们编译小的代码到一个大的、复杂的代码中,比如一个库或者一个应用程序;

我们会发现Rollup的定义、定位和webpack非常的相似:

  • Rollup也是一个模块化的打包工具,但是Rollup主要是针对ES Module进行打包的;

  • 另外webpack通常可以通过各种loader处理各种各样的文件,以及处理它们的依赖关系;

  • rollup更多时候是专注于处理JavaScript代码的(当然也可以处理css、font、vue等文件);

  • 另外rollup的配置和理念相对于webpack来说,更加的简洁和容易理解;

  • 在早期webpack不支持tree shaking时,rollup具备更强的优势;

目前webpack和rollup分别应用在什么场景呢?

  • 通常在实际项目开发过程中,我们都会使用webpack(比如vue、react、angular项目都是基于webpack的);

  • 在对库文件进行打包时,我们通常会使用rollup(比如vue、react、dayjs源码本身都是基于rollup的);

Rollup基本使用

我们可以先安装rollup:

# 全局安装
npm install rollup -g
# 局部安装
npm install rollup -D

创建main.js文件,打包到bundle.js文件中:

# 打包浏览器的库
npx rollup ./src/main.js -f iife -o dist/bundle.js
# 打包AMD的库
npx rollup ./src/main.js -f amd -o dist/bundle.js
# 打包CommonJS的库
npx rollup ./src/main.js -f cjs -o dist/bundle.js
# 打包通用的库(必须跟上name)
npx rollup ./src/main.js -f umd --name mathUtil -o dist/bundle.js

Rollup的配置文件

我们可以将配置信息写到配置文件中rollup.config.js文件:

export default {
	input: "./src/main.js",
	output: {
		format: "cjs",
		file: "dist/why.common.js"
	}
}

我们可以对文件进行分别打包,打包出更多的库文件(用户可以根据不同的需求来引入):

export default {
  input: "./src/main.js",
  output: [
    {
      format: "umd",
      name: "whyUtils",
      file: "dist/why.umd.js"
    },
    {
      format: "cjs",
      file: "dist/why.commonjs.js"
    },
    {
      format: "amd",
      file: "dist/why.amd.js"
    },
    {
      format: "es",
      file: "dist/why.es.js"
    },
    {
      format: "iife",
      name: "whyUtils",
      file: "dist/why.browser.js"
    }
  ]
}

解决commonjs和第三方库问题

安装解决commonjs的库:

npm install @rollup/plugin-commonjs -D

安装解决node_modules的库:

npm install @rollup/plugin-node-resolve -D

打包和排除lodash

output: {
  format: "umd",
  name: "whyUtils",
  file: "dist/why.umd.js",
  globals: {
    lodash: "_",
  },
},

Babel转换代码

如果我们希望将ES6转成ES5的代码,可以在rollup中使用babel。

安装rollup对应的babel插件:

npm install @rollup/plugin-babel -D

修改配置文件:

  • 注意:babel的位置应该是在commonjs的前面的;

  • 需要配置babel.config.js文件;

resolve(),
babel({
	exclude: "node_modules/**",
	babelHelpers: "bundled",
}),
commonjs(),
module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

Teser代码压缩

如果我们希望对代码进行压缩,可以使用rollup-plugin-terser:

npm install rollup-plugin-terser -D

配置terser:

const plugins = [
	resolve(),
    babel({
        exclude: "node_modules/**",
        babelHelpers: "bundled",
    }),
    commonjs(),
    terser(),
]

处理css文件

如果我们项目中需要处理css文件,可以使用postcss:

npm install rollup-plugin-postcss postcss -D

配置postcss的插件:

const plugins = [
	resolve(),
    babel({
        exclude: "node_modules/**",
        babelHelpers: "bundled",
    }),
    commonjs(),
    terser(),
    postcss()
]

处理vue文件

处理vue文件我们需要使用rollup-plugin-vue插件:

  • 但是注意:默认情况下我们安装的是vue2.x的版本,所以我这里指定了一下rollup-plugin-vue的版本;
npm install rollup-plugin-vue@4.6.1 vue-template-compiler -D

使用vue的插件:

const plugins = [
	resolve(),
    babel({
        exclude: "node_modules/**",
        babelHelpers: "bundled",
    }),
    commonjs(),
    terser(),
    postcss(),
    vue()
]

打包vue报错

在我们打包vue项目后,运行会报如下的错误:

在这里插入图片描述

这是因为在我们打包的vue代码中,用到 process.env.NODE_ENV,所以我们可以使用一个插件 rollup-plugin-replace 设置它对应的值:

npm install rollup-plugin-replace -D

配置插件信息:

const plugins = [
	resolve(),
    babel({
        exclude: "node_modules/**",
        babelHelpers: "bundled",
    }),
    commonjs(),
    terser(),
    postcss(),
    replace({
    	'process.env.NODE_ENV': JSON.stringify('production')
    })
    vue()
]

搭建本地服务器

第一步:使用rollup-plugin-serve搭建服务

npm install rollup-plugin-serve -D
serve({
	port: 8888,
	contentBase: '.'
})

第二步:当文件发生变化时,自动刷新浏览器

npm install rollup-plugin-livereload -D
livereload()

第三步:启动时,开启文件监听

npx rollup -c -w

区分开发环境

我们可以在package.json中创建一个开发和构建的脚本:

在这里插入图片描述

十二、vite工具
1、vite

什么是vite呢?

  • 官方的定位:下一代前端开发与构建工具;

如何定义下一代开发和构建工具呢?

  • 我们知道在实际开发中,我们编写的代码往往是不能被浏览器直接识别的,比如ES6、TypeScript、Vue文件等等;

  • 所以我们必须通过构建工具来对代码进行转换、编译,类似的工具有webpack、rollup、parcel;

  • 但是随着项目越来越大,需要处理的JavaScript呈指数级增长,模块越来越多;

  • 构建工具需要很长的时间才能开启服务器,HMR也需要几秒钟才能在浏览器反应出来;

  • 所以也有这样的说法:天下苦webpack久矣;

Vite (法语意为 “快速的”,发音 /vit/) 是一种新型前端构建工具,能够显著提升前端开发体验。

Vite的构造

它主要由两部分组成:

  • 一个开发服务器,它基于原生ES模块提供了丰富的内建功能,HMR的速度非常快速;

  • 一套构建指令,它使用rollup打开我们的代码,并且它是预配置的,可以输出生成环境的优化过的静态资源;

目前是否要大力学习vite?vite的未来是怎么样的?

  • 我个人非常看好vite的未来,也希望它可以有更好的发展;

  • 但是,目前vite虽然已经更新到2.0,依然并不算非常的稳定,并且比较少大型项目(或框架)使用vite来进行构建;

  • vite的整个社区插件等支持也还不够完善;

  • 包括vue脚手架本身,目前也还没有打算迁移到vite,而依然使用webpack(虽然后期一定是有这个打算的);

  • 所以vite看起来非常的火热,在面试也可能会问到,但是实际项目中应用的还比较少;

浏览器原生支持模块化

在这里插入图片描述

但是如果我们不借助于其他工具,直接使用ES Module来开发有什么问题呢?

  • 首先,我们会发现在使用loadash时,加载了上百个模块的js代码,对于浏览器发送请求是巨大的消耗;

  • 其次,我们的代码中如果有TypeScript、less、vue等代码时,浏览器并不能直接识别;

事实上,vite就帮助我们解决了上面的所有问题。

Vite的安装

首先,我们安装一下vite工具:

npm install vite -g

通过vite来启动项目:

npx vite

Vite对css的支持

vite可以直接支持css的处理

  • 直接导入css即可;

vite可以直接支持css预处理器,比如less

  • 直接导入less;

  • 之后安装less编译器;

npm install less -D 

vite直接支持postcss的转换:

  • 只需要安装postcss,并且配置 postcss.config.js 的配置文件即可;
npm install postcss postcss-preset-env -D
module.exports = {
	plugins: [
		require('postcss-preset-env')
	]
}

Vite对TypeScript的支持

vite对TypeScript是原生支持的,它会直接使用ESBuild来完成编译:

  • 只需要直接导入即可;

如果我们查看浏览器中的请求,会发现请求的依然是ts的代码:

  • 这是因为vite中的服务器Connect会对我们的请求进行转发;

  • 获取ts编译后的代码,给浏览器返回,浏览器可以直接进行解析;

注意:在vite2中,已经不再使用Koa了,而是使用Connect来搭建的服务器

在这里插入图片描述

Vite对vue的支持

vite对vue提供第一优先级支持:

  • Vue 3 单文件组件支持:@vitejs/plugin-vue

  • Vue 3 JSX 支持:@vitejs/plugin-vue-jsx

  • Vue 2 支持:underfin/vite-plugin-vue2

安装支持vue的插件:

npm install vite-plugin-vue2 -D

在vite.config.js中配置插件:

import { createVuePlugin } from "vite-plugin-vue2";

export default {
  plugins: [
    createVuePlugin()
  ]
}

Vite对react的支持

.jsx 和 .tsx 文件同样开箱即用,它们也是通过 ESBuild来完成的编译:

  • 所以我们只需要直接编写react的代码即可;

  • 注意:在index.html加载main.js时,我们需要将main.js的后缀,修改为 main.jsx 作为后缀名;

Vite打包项目

我们可以直接通过vite build来完成对当前项目的打包工具:

npx vite build

我们可以通过preview的方式,开启一个本地服务来预览打包后的效果:

npx vite preview

Vite脚手架工具

在开发中,我们不可能所有的项目都使用vite从零去搭建,比如一个react项目、Vue项目;

  • 这个时候vite还给我们提供了对应的脚手架工具;

所以Vite实际上是有两个工具的:

  • vite:相当于是一个构件工具,类似于webpack、rollup;

  • @vitejs/create-app:类似vue-cli、create-react-app;

如果使用脚手架工具呢?

npm init @vitejs/app

上面的做法相当于省略了安装脚手架的过程:

npm install @vitejs/create-app -g

create-app

ESBuild解析

ESBuild的特点:

  • 超快的构建速度,并且不需要缓存;

  • 支持ES6和CommonJS的模块化;

  • 支持ES6的Tree Shaking;

  • 支持Go、JavaScript的API;

  • 支持TypeScript、JSX等语法编译;

  • 支持SourceMap;

  • 支持代码压缩;

  • 支持扩展其他插件;

ESBuild的构建速度

ESBuild的构建速度和其他构建工具速度对比:

在这里插入图片描述

ESBuild为什么这么快呢?

  • 使用Go语言编写的,可以直接转换成机器代码,而无需经过字节码;

  • ESBuild可以充分利用CPU的多内核,尽可能让它们饱和运行;

  • ESBuild的所有内容都是从零开始编写的,而不是使用第三方,所以从一开始就可以考虑各种性能问题;

  • 等等

三、知识扩展

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.*

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

码云地址

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值