webpack的介绍

本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具
要点:模块和打包

前端需要模块化

方案有:AMD、CMD、CommonJs、ES6等规范
目前浏览只能支持ES6规范,而且大部分支持的是ES5

在webpack中可以自动将一些浏览器不支持的规范代码转化为浏览器能识别和支持的(打包)
并且通过模块化开发完成了项目后,处理模块间的各种依赖关系,并将其进行整合打包
即:webpack强调模块化开发管理,可以帮助我们进行模块化并处理模块间各种复杂的依赖关系

打包

将webpack中各种资源模块进行打包合并成一个或多个包Bundle

安装

webpack和node和npm的关系
webpack模块化打包
必须依赖node环境,就需要安装Node.js,而Node.js自带了软件包管理工具npm

(node环境为了可以正常运行代码,node环境中必须包含各种依赖的包
而npm工具(node package manager)就是为了管理node环境中各种包而存在的)

安装

全局安装和本地安装save和save-dev区别

1.npm install 本地安装
(1)将安装包放在当前目录下的 ./node_modules 下

2.npm install -g 全局安装
(1) 将安装包会放在 /usr/local下
(2)可以直接在任何地方下,通过命令行的形式进行使用

3.npm install —save 生产环境使用
(1)将安装包放在 ./node_modules下
(2)会在package.json的dependencies属性下添加安装的包名,以及版本号
(3)当程序真正运行在客户端的时候,还需要依赖到的包
(4)相当于 npm install
(5)当分享项目时,需要把 ./node_modules文件夹删除(因为里面的东西太多,分享极其不方便),接收方通过npm install,会根据packag.json自动安装好环境。

4.npm install —save-dev 开发环境使用
(1)将安装包放在 ./node_modules下
(2)会在package.json的devDependencies属性下添加安装的包名,以及版本号

npx的使用

背景:
在本地安装的包调用时候需要写明路径,否则找不到包,而全局安装的包调用时不需要写明路径
如本地调用./node_modules/.bin/webpack -v
可改为npx webpack -v


在本地使用时:
npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装!

在全局使用时:
方便了命令行上使用本地包的命令

注意:

  1. 当npx命令执行完后,包就删除了,即一次性的,临时安装在这个目录:C:\Users\用户名\AppData\Roaming\npm-cache\_npx
  2. 安装node时候路径不能有空格,否则npx失效

    webpack的使用

    万物皆为JS/webpack可以加载万物
    源码一般放在src目录
    打包部署一般放在dist目录

官网文档开始:指南

初始化webpack.config.js配置
entry和output,确定入口和输出

webpack转译JS

本地安装webpack和webpack cli
src——index.js
当前目录下命令行输入npx webpack会自动生成dist目录和其下js文件
webpack内置babel-loader,src目录下的index.js会自动转译为dist目录下对应js文件

理解文件名中hash的用途

  1. //webpack配置
  2. output: {
  3. - filename: 'bundle.js',
  4. + filename: '[name].[contenthash].js',
  5. }

上面修改的文件名设置有什么意义?便于HTTP缓存

HTTP响应头中的Cache-Control(HTTP缓存知识点)

该缓存为浏览器自带的强缓存,是HTTP协议中规定的,不需用户设置;

缓存的意义?加快页面访问速度
问题:如何更新文件?
答案:缓存是根据文件名进行的,而利用webpack打包,更改对应文件名的文件内容即可更新文件名(产生hash)。
那如何更改文件名?即以上通过md5加密技术实现的实时更名

注意:
首页html是不能进行缓存设置的,因为其他css和js文件是基于首页html作为入口进行展示的,若首页html设置一年后再更新的话不符合实际。
在设置自动化部署脚本的时候避坑:rm -rf dist; webpack 无效,';'应改为&&

用webpack生成html

如何自动更改文件名

  • 安装插件:npm install --save-dev html-webpack-plugin
  • webpack.config.js:plugins: [new HtmlWebpackPlugin()]
  • 重启webpack:npx webpack
  • 自动在生成的html文件中添加script标签引进js文件

进一步地,可以自定义html模板内容,更多配置查看文档

  1. plugins: [
  2. new HtmlWebpackPlugin({
  3. title: 'Custom template using Handlebars', //自定义默认html的title
  4. template: 'src/assets/test.html' //自定义模板文件替换默认html
  5. })

用webpack引入css

方法一:可以使用JS生成style标签插入head

  • 安装两个配套的 loader:

css-loader解析 CSS 文件后,将CSS转化为CommonJS模块
style-loader将模块的导出作为样式添加到 DOM 中,即把css-loader解析的内容变为style标签放入head中
各司其职

  • 配置webpack.config.js:

    1. module.exports = {
    2. module: {
    3. rules: [
    4. {
    5. test: /\.css$/,
    6. use: ['style-loader', 'css-loader'],
    7. },
    8. ],
    9. },
    10. };
  • 重启webpack并使用hs打开预览

方法二:也可以把css抽成文件,且自动增加和修改文件hash后缀

  • 安装插件:MiniCssExtractPlugin为每个引入JS 的 CSS 文件自动创建一个dist下的 CSS 文件
  • 配置webpack.config.js

    1. plugins: [
    2. new MiniCssExtractPlugin({
    3. // Options similar to the same options in webpackOptions.output
    4. // both options are optional
    5. filename: "[name].css",
    6. chunkFilename: "[id].css"
    7. })
    8. ],
    9. use: [
    10. {
    11. loader: MiniCssExtractPlugin.loader,
    12. options: {
    13. // you can specify a publicPath here
    14. // by default it use publicPath in webpackOptions.output
    15. publicPath: '../'
    16. }
    17. },
    18. "css-loader"
    19. ]
  • 重启webpack

通常,生产环境下比较推荐的做法是,使用 mini-css-extract-plugin 将样式表抽离成专门的单独文件。这样,样式表将不再依赖于 JavaScript

webpack-dev-server自动更新预览效果

在每次编译代码时,手动变换路径预览效果和 build 会显得很麻烦。
webpack 提供几种可选方式,帮助你在代码发生变化后自动编译代码

  1. webpack watch mode(webpack 观察模式)
  2. webpack-dev-server
  3. webpack-dev-middleware

webpack-dev-server 为你提供了一个简单的 web server,并且具有 live reloading(实时重新加载) 功能。使用如下:

  • 安装:npm install --save-dev webpack-dev-server
  • 配置webpack.config.js: ```javascript
  • mode: ‘development’, //注意先将 mode 设置为 ‘development’
  • devServer: {
  • contentBase: ‘./dist’
  • }, ```
  • 配置package.json: "start": "webpack-dev-server ",
  • 命令行输入:yarn start

注意:
webpack-dev-server 在编译之后不会自动更新dist目录,而是在内存中搞定,即读取js文件将其转译为可运行js并放在内存上,因为硬盘速度慢

根据不同环境切换不同配置

webpack引入css时候:
开发环境使用css-loaderstyle-loader因为速度快,不需要额外生成文件;
生产环境使用MiniCssExtractPlugin.loader因为可以做缓存

  • 新增 webpack.config.prod.js 文件,修改其中 use loader 为 MiniCssExtractPlugin.loader
  • 在package.json中的script中添加config指向文件:

image.png
其中,
yarn start 为开发环境,默认配置文件为 webpack.config.js
yarn build 为生产环境,添加配置文件为 webpack.config.prod.js

补充:webpack —help 可查看帮助文档
image.png

抽离共有配置webpack.config.base.js

遇到问题:
发现两个不同环境的配置存在大部分共有配置,故封装一个共有配置文件,再按需引入不同环境下的配置文件实现继承

示例:在webpack.config.prod.js中按需引入base配置
image.png

使用webpack merge库 合并不同配置

只需要一份配置文件。
而其中分模块对应不同环境
查文档学习即可

loader和plugin区别

loader 是加载器
plugin 是插件
比如,babel-loader 是用来加载高级的JS,把它变为ie支持的js
css-loader和style-loader 是来加载css文件,把它变为页面中的style标签
而插件是用来加强功能的

loader:一个文件变为另一个文件 1:1
plugin:综合多个文件变为另一个文件 n:1
不同的作用:

  • Loader直译为”加载器”。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力,即loader是用于加载某些资源文件的,它作用于一个个文件上,专注于转化文件这一领域。
  • Plugin直译为”插件”。Plugin可以扩展webpack的功能,功能更丰富,不局限于资源的加载

不同的用法:

  • Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
  • Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

用webpack引入SCSS

  • 安装:npm install sass-loader dart-sass webpack --save-dev

webpack 是 sass-loader 的 peerDependency, 并且还需要你预先安装 Dart Sass。 这可以控制所有依赖的版本,并选择要使用的 Sass 实现。

  • 修改webpack.config.js配置:
    1. // webpack.config.js
    2. module.exports = {
    3. ...
    4. module: {
    5. rules: [{
    6. test: /\.scss$/,
    7. use: [
    8. "style-loader", // 将 JS 字符串生成为 style 节点
    9. "css-loader", // 将 CSS 转化成 JS 字符串
    10. "sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
    11. ]
    12. }]
    13. }
    14. };
    特殊implementation选项决定使用哪个 Sass 实现。它需要一个 [Node Sass][] 或一个 [Dart Sass][] 模块。例如,要使用 Dart Sass,您需要修改webpack.config.js
    1. // webpack.config.js
    2. use: [
    3. "style-loader",
    4. "css=loader",
    5. {
    6. loader: "sass-loader",
    7. options: {
    8. implementation: require("dart-sass")
    9. }
    10. }
    11. ]
    12. // ...

用webpack引入LESS和Stylus

注意:目前Stylus文档较老,需要借鉴less文档格式抄

用webpack引入图片

加载 images 图像
万物皆为JS/webpack可以加载万物

假设我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处理呢?使用 file-loader,我们可以轻松地将这些内容混合到 CSS 中

  • 安装:npm install --save-dev file-loader

file-loader 将文件发送到输出文件夹,并返回(相对)URL(把文件变成文件路径)

  • 修改配置webpack.config,js: ```javascript {
  • test: /.(png|svg|jpg|gif)$/,
  • use: [
  • ‘file-loader’
  • ]
  • } ```
  • 将图片作为一个文件,放入src——assets下
  • 将图片import 进 js文件中,得到图片的路径
  • 将图片的路径放入src属性中

示例如下:

  1. //src/index.js
  2. import _ from 'lodash';
  3. import './style.css';
  4. + import Icon from './icon.png';
  5. console.log(Icon) // ------注意得到的Icon实质上是图片的‘路径’
  6. function component() {
  7. var element = document.createElement('div');
  8. // lodash,现在由此脚本导入
  9. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  10. element.classList.add('hello');
  11. + // 将图像添加到我们已经存在的 div 中。
  12. + var myIcon = new Image();
  13. + myIcon.src = Icon; // -------将图片路径放入 src属性中
  14. +
  15. + element.appendChild(myIcon);
  16. return element;
  17. }
  18. document.body.appendChild(component());

注意:
默认情况下,生成文件的文件名,是文件内容的 MD5 哈希值,并会保留所引用资源的原始扩展名。
不管什么文件,都可以使用file-loader
若引用图片,则每个图片均需要一对一引入,因为一个图片即一个文件

使用模块懒加载

原因:
某个JS文件很大 / 不希望一开始就进行加载
方法:
使用import动态导入
以下示例:点击button才进行加载

  1. button.onclick = () => {
  2. const promise = import('./lazy.js') //注意import动态导入返回的是一个promise对象
  3. promise.then(
  4. (module) => { const fn = module.default;fn()},
  5. () => {console.log('模块加载错误')}
  6. )

使用分支上传网站到github

一般方法是进入dist目录,将其部署到github网站

步骤:
git branch gh-pages 新建分支
git checkout gh-pages 进入分支 (此时该分支的文件内容与主分支一模一样)
删除源码文件,只剩下dist目录、node_module、.gitignore
move dist/* ./ 将dist目录下的所有文件移动到当前目录下
rm -rf dist 将dist目录删除
git add .
git commit -m ‘push website’
git push —set-upstrem origin gh-page

补充:浏览器页面标签处的loading状态
逆时针旋转表示 正在请求
顺时针旋转表示 正在下载

分支部署:使用bash一键部署

注意:
先提交再部署,养成习惯
参考以下示例:针对已进行第一次build的情况
image.png

Loader 和 Plugin 的实现

Loader 的使用和简单范例

手写Loader

参考官方文档:https://www.webpackjs.com/contribute/writing-a-loader/
类似手写一个功能函数

webpack.config.js配置:

匹配(test)单个 loader,你可以简单通过在 rule 数组对象设置 path.resolve 指向这个本地文件

  1. {
  2. test: /\.js$/
  3. use: [
  4. {
  5. loader: path.resolve(__dirname,'path/to/loader.js'),
  6. options: {/* ... */} //可添加额外的参数
  7. }
  8. ]
  9. }
  10. //当test匹配到对应格式的文件时,执行path.resolve指定目录下的loader文件,
  11. loader文件实则导出为一个函数,在函数中可以定义相关转化内容

匹配(test)多个 loaders,你可以使用 resolveLoader.modules 配置,webpack 将会从这些目录中搜索这些 loaders,例如,如果你的项目中有一个 /loaders 本地目录,此方法配置后,后期陆续添加其他loader较方便,只需要:loader:'loader的文件名'

  1. resolveLoader: {
  2. modules: [
  3. 'node_modules',
  4. path.resolve(__dirname, 'loaders')
  5. ]
  6. }
  7. //或者写成:
  8. resolveLoader: {
  9. modules: [
  10. modules:['node_modules','loader'] //先去node_modules目录下查找,找不到再去loader目录找
  11. ]
  12. }

loader文件编写

image.png
个人理解:
loader 实际上 是导出为一个函数的 node 模块 ,只不过很多时候在需要处理复杂内容的情况下,需要添加了一些额外的参数,比如loader-utils中getOptions的使用:

实战

replace-loader

replace-loader 提供指定内容替换功能
假设需求:
把所有js文件中的’abc’转为大写的ABC
解决方法:
我们需要在webpack配置的options传入abc告诉loader

  1. {
  2. test:/\.js$/,
  3. use:[
  4. {
  5. loader:path.resolve(__dirname,'loader/replace-loader.js'),
  6. options:{
  7. name:'abc'
  8. }
  9. }
  10. ]
  11. }

那么loader如何获取到这个options传来的参数abc呢?使用loader-utils里面getOptions方法
它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项。

  1. import { getOptions } from 'loader-utils';
  2. module.exports= function(source){
  3. let options = getOptions(this)
  4. console.log(options)
  5. return source.replace(options.name,options.name.toUpperCase())
  6. }

markdown-loader

假设需求:
我们想要将我们在开发环境编写的md文件最终变成html文件,并嵌入首页html文件中统一展示,用webpack打包后会生成dist目录下的html文件
解决方法:
借助markdown第三方库,并加入webpack配置,选择性提供参数配置

前提:

  • 首先使用HtmlWebpackPlugin插件,通过这个插件,将指定html文件作为模板template进行渲染,用于webpack打包生成的html文件

    1. plugins:[
    2. new HtmlWebpackPlugin({
    3. filename:'index.html',
    4. template:'./src/views/index.html',
    5. title:'haha'
    6. })
    7. ]
  • 创建 article.md文件用以嵌入index.html ```html <!DOCTYPE html>

    <%= require(‘./article.md’) %>

  1. 正文:<br />markdown-loader <br />webpack配置:
  2. ```javascript
  3. module:{
  4. rules:[
  5. test:/\.md$/,
  6. use:[
  7. { loader:'html-loader'},
  8. { loader:'markdown-loader',
  9. options:{
  10. html:true
  11. }
  12. },
  13. ],
  14. ]
  15. }

markdown-loader.js文件:

  1. import { getOptions } from 'loader-utils';
  2. import validateOptions from 'schema-utils';
  3. //用于校验用户使用插件时候传递的参数options是否正确,在编译的时候进行校验,比如某些参数是固定的情况下
  4. import MarkdownIt from 'markdown-it'
  5. const schema = {
  6. type:'object',
  7. properties:{
  8. html:{type:'boolean'}, //支持在md文件中直接写html
  9. linkify:{type:'boolean'} //支持在md文件中写link,会转化为a标签
  10. }
  11. }
  12. module.exports = function (source) {
  13. const options = getOptions(this)
  14. const md = markdownIt(options)
  15. validateOptions(schema,options) //校验
  16. return md.render(source) //同步返回,是同步loader
  17. }

核心功能:把markdown文件变成html格式的文件,故需要引用第三方库markdown-it,在npm下载即可

loader发布上线:

npm init -y
根目录创建index.js,将markdown-loader文件的内容全部拷贝至此
安装index.js需要用到的第三方库和插件,本项目用到的为 loader-utils 、 schema-utils 、 markdown-it
根目录创建 readme.md

  1. //告诉别人怎么用这个markdown-loader
  2. //将webpack.config.js内容拷贝至此即可

可修改package.json中的描述和关键词信息
npm publish
若对包有修改则必须修改版本号version后再发布

后面可模拟安装和使用自己的包

异步loader

以上的loader都是同步的loader,
对于异步 loader,使用 this.async 来获取 callback 函数

  1. module.exports = function (source) {
  2. const options = getOptions(this)
  3. const md = markdownIt(options)
  4. validateOptions(schema,options) //校验
  5. let callback = this.async() //异步loader
  6. setTimeout(()=>{
  7. callback(null,source) //返回单个异步值
  8. },3000)
  9. //this.callback(null,md.render(source),...另外的参数) //异步loader,返回多个异步值
  10. //return md.render(source) //同步返回,是同步loader
  11. }