webpack从入门到精通

开言

webpack作为现代前端开发最火的模块打包工具,已经成为了前端工程师必备的技能之一。

是:
前端资源构建工具;
静态模块打包器;
webpack从入口文件开始,根据模块的依赖关系进行分析,然后生成加工后的静态资源;
将高级语法转换成兼容性高的通用语法;
引入chunk块概念–打包–>bundles;
基于nodejs平台的工具,遵循commonjs模块化规范

本章重点讲解

  • webpack配置参数,
  • 开发环境配置,
  • 生产环境配置,
  • 企业级的优化环境配置
  • 等等

预备技能

  • 基本Nodejs知识和Npm指令;
  • 熟悉ES6语法

环境参数

  • Nodejs10版本以上;
  • webpack 4.26版本以上

知识点

  1. loader
  2. plugin
  3. HMR
  4. devtool
  5. resolve
  6. optimization
  7. code split
  8. caching
  9. lazy loading
  10. shimming
  11. library
  12. dll
  13. mode
  14. eslint
  15. babel
  16. pwa
  17. webpack4 /webpack5

webpack的五个核心概念

  1. entry:webpack从该入口开始分析构建内部依赖图,并打包。
  2. output:打包后的bundles输出到哪里,以及文件名。
  3. loaders:webpack本身只认识JS,需要借助loaders将来处理各种非JS资源
  4. plugins: 打包优化,压缩,开发辅助,服务器启动等等
  5. mode:不同环境的预设。默认是‘production’。会将值赋值给一个全局变量:process.env.NODE_ENV。webpack根据这个值,启用不同的plugin和loader等等

安装

npm i -D webpack webpack-cli

webpack-cli是做什么的?
如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack,你还需要安装 CLI。

通过查看webpack脚本,得出webpack命令,本质就是调用了webpack-cli来实现的。webpack4+将这个功能单独拆解出来了。

const runCli = cli => {
     
	const path = require("path");
	const pkgPath = require.resolve(`${
       cli.package}/package.json`);
	// eslint-disable-next-line node/no-missing-require
	const pkg = require(pkgPath);
	// eslint-disable-next-line node/no-missing-require
	require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

/** @type {CliOption} */
const cli = {
     
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};
if (!cli.installed) {
     
	const path = require("path");
	const fs = require("graceful-fs");
	const readLine = require("readline");

	const notify =
		"CLI for webpack must be installed.\n" + `  ${
       cli.name} (${
       cli.url})\n`;

	console.error(notify);
	// ...
} else {
     
	runCli(cli);
}

执行

在cli中执行入口文件。

# 因为没有全局安装webpack,所以需要制定本地webpack
./ node_modules/.bin/webpack ./src/index.js 
# 如果global安装了
webpack ./src/index.js

通过npm脚本执行-package.json:

scripts: {
     
	// npm 会主动去查找环境中的webpack
	"webpack": "webpack ./src/index.js"
}

上面全部使用webpack的默认配置。

自定义配置

在目录下新增:webapck.config.js,webpack默认会在项目根目录下查找这个文件,当作自定义配置项。

module.exports = {
     
  mode: 'development'
}

如果放在其他的地址,需要在脚本中显式指定。如webpack/dev.js

scripts: {
     
	"webpack": "webpack --config ./webpack/dev.js ./src/index.js"
}

其他的配置项,可以放到配置文件,也可以当作脚本参数传入。【推荐:复杂的放到配置文件】

测试打包后资源是否可用

原始代码:

// demo1.js
const greeting = 'Hello World!';
console.log(greeting);

// index.js
import './demo1'

执行脚本webpack后:

npm run webpack

默认情况下,输出文件为:dist/main.js

node dist/main.js
# 成功输出
# Hello World!

结论:打包js成功了!

webpack默认支持js/json文件,可以省略文件后缀。

如果想要打包css文件呢?

// style1.less
body {
     color: red}

// index.js
import './style'

报错如下:

[no extension] src/style1 doesn't exist
[.js] src/style1.js doesn't exist
[.json] src/style1.json doesn't exist
[.wasm] src/style1.wasm doesn't exist
[as directory] src/style1 doesn't exist

用所有默认支持的格式去匹配,都找不到该文件。
然后,指明文件后缀

import './style1/css'

报错如下:

Module parse failed: Unexpected token.
You may need an appropriate loader to handle this file type...

结论:需要安装loader

打包其他资源

css资源

需要借助loader,配置webpack.config.js

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

当webpack匹配到css结尾的模块时:

  1. 先使用css-loader将css文件处理成commonjs模块,整合到js中;
  2. 再用style-loader,创建一个style标签,将上面处理后的css代码插入标签中,当有html文件使用到这个打包文件时,将style标签,插入到head尾部

html资源

使用html-webpack-plugin插件:

npm i -D html-webpack-plugin

使用:

const htmlWebpackPlugin = require('html-webpack-plugin')
// plugin: 下载 引入 使用
 plugins: [
   // html-webpack-plugin
   new htmlWebpackPlugin()
 ]

执行:npm run webpack
结果:在output指定目录下,生成了一个index.html文件。并且将bundle文件–main.js插入到了script标签中,默认将script放在head中。

图片资源

如果在css中引入了图片资源:

background-image: url(./images/image.png);

那么需要用到url-loader(用来处理css文件中url相关图片文件的)和file-loader【看情况安装】:

{
     
  test: /\.png$/,
  loader: 'url-loader',
  options: {
     
    // 默认情况limit没有限制;下面限制为:当图片小于1kb,才会用url-loader进行处理,将图片转化为base64编码字符串【优点:可以减少http请求;缺点:加大js文件体积】
    // 当图片大雨1kb时,url-loader就需要依赖file-loader包,将图片直接copy到dist目录下,默认资源名字会变成hash名字
    limit: 1 * 1024
  }
}

如果在html中引入image标签,上面的处理就会出问题了,因为,例如file-loader会修改图片的名字,此时,html会找不到图片资源,我们需要用到html-loader

 {
     
   test: /\.html$/,
  // 处理html中的img标签资源,负责将img资源引入,然后才能被url-loader处理。
  // html-loader引入图片是es6规范的,解析时会报错
  // 解决办法:将html-loader的esModule设置为false
  loader: 'html-loader',
  options: {
     
    esModule: false
  }
}

其他资源(字体等)

不需要做压缩等处理,只需要,复制并输出。
比如字体文件:iconfont.ttf iconfont.svg

方案:用排除法匹配其他资源,然后用file-loader处理。匹配到的资源,将会用hash重命名后,输出到dist目录

rules: [
{
     
  exclude: /\.(html|css|js)/,
  loader: 'file-loader'
}
]

devServer

开发服务器:用来自动构建、打开浏览器,并自动刷新等功能。大大提升了开发效率。

 // 开发服务器只在内存中编译打包,没有任何输出
  // 用npx webpack serve
  devServer: {
     
    contentBase: './dist',
    // 启动gzip
    compress: true,
    port: 3000
  }

需要安装webpack-dev-server。

npm i -D webpack-dev-server

当你改动源代码的时候,webpack就会自动打包,并更新页面

不同环境的配置(开发/生产/测试)

一般情况下,webpack已经针对不同环境进行了默认的配置配置,只需要用mode去开启就行了。

所以脚本上,我们需要传入不同的参数,或者是建立不同mode的相关config文件。

// 如果只有一个webpack.config.js文件,那么我们可以通过给cli传入参数
"dist": "webpack --env=dist"
"dev": "webpack --env=dev"
// 使用
const {
     env} = require('minimist')(process.argv.slice(2), {
     
  string: ['env'],
});
// 然后根据env给webpack的配置项传入不同的值。

// 或者可以将配置文件指定为:dev.config.js/dist.config.js等,然后在脚本中使用不同的文件
"dist": "webpack --config=webpack/dist.config.js"
"dev": "webpack --config=webpack/dev.config.js"

针对不同环境,我们可以借助一些分析工具,如:

  1. webpack-bundle-analyzer 开发环境分析工具
  2. webpack-dev-server 开发服务器
  3. webpack-manifest-plugin 生产环境,可能还需要借助这个工具
  4. optimize-css-assets-webpack-plugin 生产环境css文件压缩
  5. mini-css-extract-plugin 拆分css
  6. 等等

此外我们还可以考虑:

  1. css/js 兼容不同浏览器的处理工具,如css前缀添加工具【postcss-loader,它会去package.json中查找browserList配置,然后生成不同的兼容性前缀,需要手动设置process.env_NODE_ENV】等
  2. 将css/js生成不同的文件【那就不能用style-loader来创建style标签,而要用mini-css-extract-plugin.loader提取出css,然后生成main.css,用link引入】;或者将文件css+js整合成一个文件【css-loader–将css整合到js文件中】
  3. splitChunk: 将依赖的文件,如jquery,antd等大文件,排除不打包到main.js
  4. 或者用externals属性,去排除一些包,然后用script的方式手动引入CDN中的代码

JS的语法检查:

常用的语法检查工具是eslint。我们可将eslint嵌到webpack中,用eslint-loader【以来esling】

rules: [
	{
     
	    // 注意:只检查源代码,需要排除node_modules中的代码
		test: /\.js$/,
		exclude: /node_modules/,
		loader: 'eslint-loader',
		// 手动配置不同的规则,或者使用第三方的规则,如:airbnb
		// npm i -D eslint-config-airbnb-base eslint-config-import
		// 将airbnb配置到package.json中:eslintConfig: {extends: 'airbnb-base'}
		options: {
     
			// 自动修复
			fix: false
		}
	}
]

然后执行webpack,如果js代码中有些语法不符合airbnb的规范,那么webpack操作就会失败,并输出:错误

如果将fix设置为true,那么错误会自动处理,webpack大部分情况不会被打断,除非eslint处理不了。

JS兼容性处理,如兼容IE10:

正常情况下,源代码如果使用es6及以上语法,webpack并不会进行转换,此时若将bundle直接放到低版本浏览器(IE)中运行,脚本会报错,如:不认识const等等。

那么我们需要用到babel:

npm i -D babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
rules: [
{
     
	test: /\.js$/,
	excludes: /node_modules/,
	loader: 'babel-loader',
	options: {
     
		// 预设:指示babel做什么样的兼容性处理,preset-env就是基本处理,将语法转化为es5及以下兼容性强的语法【基本语法】
		// presets: '@babel/preset-env'
		
		// 如果用到promise等语法,那上面的preset-env并不能处理,就需要其他的包:@babel/polyfill 在源代码入口引入就可以了。
		// 问题:我只需要做一部分兼容性处理,但引入@babel/polyfill体积太大了。。。。那么就需要做按需加载---core-js。然后不需要引入:@babel/polyfill
		presets: [
		'@babel/preset-env',
		{
     
			// 按需加载
			useBuiltIns: 'usage',
			// 指定版本
			corejs: {
     version: 3},
			// 指定兼容到哪个版本的浏览器
			targets: {
     chrome: '60', firefox: '60', ie: '9'}
		}
		]
	}
}
]

所以最终方案为:@babel/preset-env + core-js

JS压缩

webpack开启:

mode: 'prodcution'

HTML压缩

配置插件:

plugins: [
  // html-webpack-plugin
  new htmlWebpackPlugin({
     
    template: './index.html',
    title: 'Demo',
    inject: 'body',
    minify: {
     
    	// 如:移除空格等等
		collapseWhitespace: true,
	}
  })
]

优化

需要考虑哪些点呢?需要考虑如下两类:

1. 开发环境优化

  • 优化打包构建速度
    • devServer也是一种优化
    • HMR(只打包修改了的模块–模块热更新) ,在devServer中设置:hot: true来开启。
      • css文件可以使用HMR功能;
      • JS和html不能使用,并且导致html文件不能热更新了,
      • 那么需要修改entry,将html文件引入。
      • 如果修改html,那么项目就会全部刷新。。
      • 那么html只有一个:需要进行热更新吗???? 不需要
      • JS需要HMR吗?需要,那就在入口js文件中加入热更新代码
      • 只能处理非入口js文件,入口文件修改,肯定会全部打包
    • 开启缓存
      • babel-loader设置缓存 cacheDirectory: true 【第二次构建时才会读取缓存】
  • 优化代码调试功能借用source-map技术
    • sourcemap是:一种源代码到构建后代码的映射技术 ,能提供准确的错误信息,及错误在源代码发生的位置
    • inline-source-map
      • 只生成一个内联sourcemap文件【速度更快】
    • hidden-source-map
      • 外部【速度相对较慢】
    • eval-source-map
      • 为每个文件都生成一个内联的sourcemap文件
    • 直接使用webpack的devtools
    • 速度:eval>inline>cheap>…
    • 调试最友好:source-map>cheap-module-source-map
    • 中和速度和友好度,推荐选择:eval-source-map
// html不需要HMR 
entry: ['./src/index.js', './index.html']
devtool: 'inline-source-map'

// index.js
if(module.hot) {
     
 // 如果开启了HMR,则监听下面模块,如果变了,就只打包下面模块
 module.hot.accept('./...js', function(){
     })
}

2. 生产环境优化

  • 优化打包构建速度
    • 开启缓存
      • babel缓存 cacheDirectory: true 【第二次构建时才会读取缓存】
      • 多进程打包:一般是给babel-loader用,使用tread-loader
  • 优化代码运行的性能【进程开启600ms、进程通信都需要开销只有工作消耗时间长,才用
    • 文件资源缓存 启动一个server。或者设置 static静态资源 的maxAge: 10000强制缓存时间为10000ms。同时配合hash值来替换进行资源比较【记得区分css和js的hash生成规则
    • tree-shaking:这个概念的意思就是:删除未使用到的代码,从而达到减少bundle体积的目的。【必须:es模块化+production】【自动开启
      • 如何package.json中设置了sideEffect: false ,表示源代码中没有副作用,那么所有相关代码都会被shaking掉,从而,引发问题,可能会把import ‘xxx’这样方法引入的文件删掉。需要将配置改为:sideEffects: [x.css] ,面对不同版本的webpack,tree-shaking的功能可能会有差异,如果需要开启该功能,建议将sideEffects指明
    • code-split:优点:代码并行加载;减小单个文件的大小;是按需加载的基础。实现方式:
      • 多入口。输出多个bundles。【一般用于多页面应用的场景】
      • 配置optimization.splitChunk
rules: [
  {
     
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
      // 开启多进程打包
      'tread-loader',
      {
     loader: 'babel-loader', options: {
     presets: []}}
    ]
  }
]

optmization : {
     
  splitChunk: {
     
    // all的意思:将node_modules中代码打包成一个单独文件:vendor.js
    // 多入口文件,会共用vendor.js
    // 我们还可以将node_modules中包,拆分成单独的包,例如xlsx太大,可以单独引入
    chunks: 'all'
  }
}
  • lazy-loading:使用es6的import()方法实现。需要设置webpackChunkName将其打包成单独的文件才能支持懒加载。
  • prefetch: 设置 webpackPrefetch: true实现延迟加载的包预加载。当再使用代码import()时,读取的就是预加载进来的缓存【code: 200】【prefetch:等其他资源加载结束后,浏览器空闲了才加载,不会阻塞正常资源的加载;移动端、IE兼容性差—不推荐】
  • PWA:service work + cache,兼容性差;
    • 检测一个站点有么有使用pwa技术:f12–network-offline,然后刷新页面。
    • offline时,资源请求成功的状态为:200 (from service worker)
    • 怎么实现?使用workbox-webpack-plugin
plugins: [
  new workboxWebpackPlugin.GenerateSW({
     
    // serviceWorker快速启动
    // 删除旧的serviceWorker
    // 生成一个serviceWorker配置文件
    clientClaim: true,
    skipWaiting: true
  })
]

// 入口文件 index.js中注册serviceWorker
// 1. eslint默认不认识navigator/window全局变量,需要改package.json中eslintConfig.env: {browser: true},表示支持浏览器的变量
// 2. ==SW的代码必须运行在服务器上==,而不能直接通过file://协议访问。
if('serviceWorker' in navigator) {
     
	window.addEventListener('load', ()=> {
     
	// service-worker.js这个文件就是上面webpack生成的配置文件
	  navigator.serviceworker.register('/service-worker.js')
	    .then((success) => {
     
	      console.log(success)
	    })
	    .catch((error) => {
     
	      console.log(error)
	    })
	})
}
  • externals:排除掉一些包不进行打包,而用cdn的方式等引入
externals: {
     
  // 库名: npm包名
  jquery: 'jQuery'
}
  • dll技术:动态链接库。指定某些库不进行打包。并将某个包打包成多个文件,比splitChunk: all更优化。webpack.DllPlugin()【没研究】
  • 生产环境源代码要不要隐藏呢??
    • 肯定不能用 内联 的sourcemap,会让代码体积过大

你可能感兴趣的:(webpack)