1

webpack 4 慕课网全套课程学习笔记

1、安装webpack-cli会自动帮我们安装webpack

2、npx webpack index.js 让webpack帮我们翻译index.js(会帮我们翻译和链接ES6的模块,翻译成浏览器能认识的代码)

3、webpack初步认识:JS代码的翻译器。webpack只认识import这样语句,其他高级JS语法它一概不认识。所以称作JS代码的翻译器是高看了它。实际上,webpack的核心定义是模块打包工具。把不同的模块打包到一起。

4、webpack对符合ES Module规范模块、CommonJS规范模块、CMD、AMD模块都能正确认识。

5、webpack发展:纯JS模块打包工具—》css,scss, png, jpg等模块打包

6、npm init完成后配置package.json:

增加“private“:true 属性,说明该项目是私有项目,npm就不会放到共有云上去

去掉”main”: index.js字段,因为我们的项目不会被别人引用,只是自己来用,没有必要向外暴露一个JS文件

7、有时候安装webpack不成功,是因为npm源被国内墙掉了,解决问题方法:用手机分享WiFi热点,再去安装就没问题了。不推荐全局安装webpack(npm install webpack webpack-cli -g),会无法同时使用两个版本的webpack。推荐局部安装(npm install webpack -D // -D 等同于 —save-dev),局部安装无法直接执行webpack命令(如webpack -v), 我们可以执行npx webpack -v, npx会帮我们在当前目录的下node_moudles的包。需要webpack-cli才能支持在命令行执行webpack命令。

8、npm info webpack或其他包:可以看到某个包有哪些版本,npm install webpack@4.16.5安装指定webpack版本。

9、webpack默认配置文件:webpack.config.js。 配置中output.path:定义打包出来的文件存放在哪里,必须是绝对路径,如 path.resolve(__dirname, ‘bundle’)。

output.path定义的绝对路径,是整个工程所有打包输出文件的存放根目录。

不同资源的loader配置的options.outputPath定义的相对路径,是相对根目录output.path的存放路径。

10、webpack打包命令行结果提示中的chunks 指的是chunk的ID值,后面的chunk name指的是名称,默认main。因为entry:’./src/index.js‘ 实际上是 entry: { main: ‘./src/index.js’ } 的简写。

webpack的默认模式mode是production,打包出的文件会被压缩。mode的另一个值是development,打包出的文件不会被压缩。

11、webpack官方推荐的loaders和plugins都有很多,配置也有很多。加上个人和机构开发的,有几万个,想全都学会,基本不可能,所以需要套路,学会核心的东西,在实践当中,去查看文档。当然这个方法还不是很好,更好的方法是群里发出来讨论,不用查,直接抛出来,因为每个人处理的业务场景不同,这样大大提高开发效率。

12、webpack只认识JS文件的打包,其他类型的文件需要在module: { rules: [{},{}…] }配置里面告诉webpack怎么处理。

13、webpack遇到图片文件,会求助file-loader, 这个loader会把图片挪到指定的dist目录,然后改个名称(名称可以配置),再返回新的名称给webpack。根据这个原理,file-loader可以打包任何的静态文件,如svg, excel,txt文件,因为静态文件的打包处理大都是直接复制到打包输出目录。

14、var img = new Image() // 这句代码创建了一个image标签

img.src = require(‘./avatar.png’) // 图片经过file-loader处理,返回图片在dist目录的hash名称

img.classList.add(‘avatar’) // 添加样式class

document.getElementById(‘root’).append(img) // 图片插入页面

15、静态资源打包之file-loader参数配置:

use: {
  loader: ‘url-loader’,
  options: {
    name: ‘[name]_[hash].[ext]’, // 使用老静态资源的名字和后缀名。[xxx]称之为placeholder,占位符.
    outputPath: ‘images/’ // 存放于dist目录(webpack配置中output.path配置整个工程dist目录)
  }
}复制代码

16、url-loader: 和file-loader非常类似,只不过多了一个limit的配置项。url-loader完全实现了file-loader能做的,不同之处会根据limit阈值把图片转为base64字符串,打包bundle.js里面。

好处是省了一次HTTP请求,问题是图片很大时,bundle.js很大,页面加载很久才能出来。

url-loader参数配置(和file-loader完全一样,只是多了个limit配置项):

options: {
    name: ‘[name]_[hash].[ext]’,
    outputPath: ‘images/’
    limit: 2048 // 单位字节,小于2kb的才打包进入bundle.js
  }复制代码

17、静态资源打包之(css样式篇)

use: {

loader: [‘style-loader’, ‘css-loader’] // 因为需要2个loader,就不能写成对象值形式,要用数组形式。

}

head里面的style标签,是style-loader帮我们挂载上去的,所以在css-loader处理css文件时需要配合style-loader使用。

18、使用先进css语法之sass-loader: 需安装sass-loader node-sass

use: {

loader: [‘style-loader’, ‘css-loader’, ’sass-loader’] // loader是从右到左的执行顺序

}

19、产商前缀自动添加之postcss-loader: 给CSS3新属性自动添加 moz- webkit- 等厂商前缀

需新建postcss.config.js配置文件:

plugins: [require(‘autoprefixer’)] // 配置需要使用的插件,当postcss-loader被使用的时候,会去使用一

// 个叫autoprefixer的插件,帮我们去加上厂商前缀

webpack配置:

use: {
    loader: [‘style-loader’, {
       loader: ’css-loader’, // 如果需要多个loader,还要给loader传参数,就不能写字符串了,要写对象
       options: {
          importLoaders: 2, // 通过import方式引入的资源也要去走2个loader,sass-loaderpostcss-loader
          modules: true // css module,模块化css让JS模块引入css只在当前模块有效,对其他引入模块无效
    }
     }, ’sass-loader’,’postcss-loader’] 
}
复制代码

20、字体文件(www.iconfont.cn 下载iconfont.css)静态资源,需要file-loader打包拷贝至目标目录。下载字体并拷贝样式至项目详细,参考lesson 3-4。

webpack官方文档介绍:DOCUMENTSTION目录下的Asset Management子目录,介绍了所有的资源文件打包,以及数据文件(如.csv格式的Excel表格)打包的解决方案。

21、使用plugins让打包更便捷:

1、安装html-webpack-plugin,然后require引入,再webpack配置:

(这个插件会在打包结束后自动生成一个HTML文件,并把打包生成的JS自动引入到这个HTML文件中。)

2、安装clean-webpack-plugin, 然后require引入,再webpack配置:

(这个插件会在打包前自动清除指定目录)

plugins: [
        new HtmlWebpackPlugin({ template: ‘src/index.html’ }), // 支持非常多的配置参数,详细看官网
    new CleanWebpackPlugin([‘dist’]) // 也可以用linux命令放至脚本中实现
    ]复制代码

22、Entry和Output基本配置

entry写字符串,等于写键值对(默认的键名是main),如果output.filename指定,覆盖main作为文件名。

output:output.publicPath: ‘cdn.com.cn’ // 所有页面引入的JS都会加上这个地址。当我们的项目,后台服务器用index.html,而静态资源放到CDN的情况下,就会用到output.publicPath配置项。

23、SourceMap配置: sourceMap是一个映射关系,它知道dist目录下main.js文件中的96行实际上对应的是src目录下index.js文件中的第一行。方便我们去源代码排错,而不是去目标代码(打包出来的)排错。

mode: ‘development’, // development模式下自动配置了sourceMap

devtool: ‘none’ 或 ‘eval’ 或 …… // none表示关闭sourceMap.

eval: 以eval方式执行代码,map映射放在js里。虽包速度最快,不生成.map文件,但不适于复杂代码。

cheap-source-map : cheap的意思是只标记出错行,不标记列。只管业务代码的出错,不管第三方插件或loader的出错。

cheap-module-source-map: module的意思是既管业务代码的出错,还管第三方模块,loader出错等。

inline-source-map: inline的意思是把.map文件合并到打包生成的目标js里,以base64编码放在末尾。

最佳实践:

开发环境:cheap-module-eval-source-map。提示较全,打包较快。

线上环境:cheap-module-source-map。

24、使用WebpackDevServer提升开发效率:源码改变,实现自动重新打包。

方式一:命令webpack —watch // watch参数会监听文件改变,自动打包,但需手动刷新浏览器

方式二:安装webpack-dev-server,配置参数:

devServer: { // webpackDevServer会帮助我们启动HTTP一个服务器
      contentBase: ‘./dist’, // 服务器的根目录,就是当前的打包输出目录
      open: true // 自动打开浏览器,并访问localhost:8080
      port: 8080,
      overlay: true,
      proxy: {
        ‘/api’: ‘http://localhost:3000' // 访问localhsot:8080/api 地址会直接转发到 localhost:3000
      }
    }复制代码

命令:webpack-dev-server // 会监听文件改变,自动打包,同时自动刷新浏览器

vue 和 react的底层都使用了webpack-dev-server,所以在vue-cli和react-cli的官方脚手架都同样配置了。

方式三:自己写服务器监听文件改变,自动打包。

1、安装webpack-dev-middleware(做监听) express(做HTTP服务器)
2、const express = require(‘express’)   webpackDevMiddleware= …  webpack= … config= …
3、const compiler = webpack(config)  // 返回一个编译器,编译器执行一次,重新打包一次代码
4、const app = express()  app.use(webpackDevMiddleware(compiler, { 
     publicPath: config.output.publicPath
   }))
  app.listen(8080, () => { console.log(‘server is running’) })
复制代码

25、webpack Hot Module Replacement 热模块更新

div:nth-of-type(odd) { background-color: blue; } // 偶数的子元素背景为蓝色 

 webpac-dev-server帮我们实现自动打包和自动刷新浏览器,但如果我们只改变了某个模块的代码,也会浏览器重新请求页面导致整个页面刷新,这样之前做的交互就会丢失,交互又得重做一遍。 配置参数:

 plugins: [new webpack.HotModuleReplacementPlugin()] // 通过HMR插件实现HMR

 改动的模块重新请求下来后,不会自动执行,需要在引入模块的代码中加入下代码,把更新的模块重新执行一遍。css模块不用自己写这样的代码,是因为css-loader内置了这样的代码。原则上所有想实现热模块更新类型的文件都需要写这样的代码,不需要写的是已经内置了。 

if(module.hot) {   
  module.hot.accpet('./number.js', () =>  
    number();  
  }) 
} 复制代码

当你在代码中去引入其他模块的时候,如果希望某个模块代码发生变化,只希望更新这个模块,就要用到HMR技术。

26、使用babel处理ES6语法(参考Babel官网)

低版本IE浏览器和国产浏览器没有像Chrome与时俱进,更新慢。

ES6转ES5:可以 let 转 var () => {} 转普通函数 function() {}

不可以转:Promise变量和map语法等 需要借助babel-polyfill,在所有代码运行前补充到低版本浏览器里

import “@babel/polyfill” // 在main.js文件头引入模块,这样代码会内联展开并早于业务代码执行

需要安装: babel-loader @babel/core babel核心库,识别JS代码,把JS代码转换为AST抽象语法树,再将AST树编译转化为新的语法。 babel-preset-env包含了所有ES6转ES5的规则。

rules:[{
    test: /.js$/,
    exclude: /node_modules/, // node_modules目录里面是第三方代码,第三方模块自己做过ES6转ES5了
    loader: ”babel-loader”,
    options: {
      presets: [“@babel/preset-env”] // 不传参写法,下面传参写法
      presets: [[“@babel/preset-env”, { useBuiltIns: ‘usage’ , 
        targets: {
          chrome: ‘67’, // 项目会打包运行在大于67版本的浏览器下,让babel根据情况是否转换和填充
          firefox: ’60’, edge:’17’, safari: ’11.1’….
        }
      }]] 
    }
  }]复制代码

useBuiltIns:usage 当用babel-polyfill做填充的时候,根据业务代码用到什么才加什么。减少打包体积。

(webpack4中配置了useBuitin:’usage’的话,webpack会自帮我们引入babel-polyfill,无需手动引。)

通过以上配置,在我们代码中写任何ES6的代码都不会有任何问题了。但是在开发类库、第三方模块、组件库的时候,用babel-polyfille的方案有问题,因为在注入Promise、map等方法时会全局注入,污染全局环境,这时需要换一种配置方式,插件的方式。

(1)安装@babel/plugin-transform-runtime和@babel-runtime,不引入babel-polyfill避免污染全局环境,

(2)更改babel-loader的options配置:(建议是babel配置项非常多,单独建.babelrc配置文件更合适)

options: {
  “plugins”: [[“@babel/plugin-transform-runtime”, {
      “corejs”: 2, // 默认false,改成2需要额外安装@babel/runtime-corejs2代替@babel-runtime
       “helpers”: true, 
       “regenerator”: true,
       “useESModules”: false
    }]]
}复制代码

27、配置React代码的打包 (待看)

28、Tree Shaking

当index.js引入a.js(a.js中有多个方法)中的某一个方法,webpack默认会把a.js中所有方法打包进来。增加了chunk体积。webpack2.0以后提供了摇树功能,但Tree Shaking只支持ES Module(import方式引入的ES Module底层是静态引入,require 引入的Common JS模块是动态引入。)

摇树配置:development模式需要手动配,配好了代码体积也不变少 production模式下默认配好了

(1) webpack.ptimization: {
      usedExports: true,
    }
(2) package.json中配置:”sideEffects”: false, // 避免 import “babel-polyfill”形式引入全部代码被忽略
  或指定具体免忽略清单: “sideEffects”: [‘@babel/poly-fill’, ‘*.css’] // *.css避免忽略任何css复制代码

29、development和production模式的区分打包:production模式下不需要DevServer和new webpack.HotModuleReplacementPlugin(),代码会压缩,sourceMap配置不一样。

配置抽取:webpack.common.js webpack.dev.js webpack.prd.js 通过安装webpack-merge插件合并。

30、Webpack和Code Spliting

plugins: [
    new CleanWebpackPlugin([‘dist’], {
      root: path.resolve(__dirname, ‘../‘) // 指定根目录,该插件默认把当前目录作为根目录
    })
  ]复制代码

安装lodash 工具集合包 import _ from ‘lodash’

默认Webpack 会把工具包和业务代码打包到一个文件,打包文件会很大,加载时间会长,且业务代码一变更打包结果跟着变更,实际上lodash第三方工具包并没有变更。

解决办法(Code Spliting):

第一种:自己分割 (代码分割和webpack无关,在webpack前我们手动分割代码到不同JS文件,然后根据依赖关系顺序引入JS文件)

将第三方工具包拆出来,新建common.js文件,在common.js文件引入各个第三方工具包,并挂在到window全局对象上,然后在页面的entry入口新增一个:

entry: {
  common: ’./src/common.js’,
  main: ‘./src/index.js’
}复制代码

这样打包出来后,index.html会先后引入common.js和main.js,执行结果和将第三方工具包和业务代码打包到一个文件一样。

第二种:webpack中实现代码分割,两种方式

(1)同步代码:只需在webpack中做optimization的配置,借助Webpack插件SplitChunkPlugin实现

optimization: {
     splitChunks: { 
        chunks: ‘all’ // 同步代码需要这样配置才会代码分割,异步代码webpack会自动代码分割到单独文件
      }
  }复制代码

遇到如下代码,webpack会去分析代码,把该提取出来的文件提取出来单独存放:
import xxx from ‘xxx’

// 接着是写业务代码

(2)异步代码:具体通过JSONP异步加载文件,只要是异步加载,webpack会自动做代码分割单独存放到一个文件里去。如下

function getComponent() {
   return import(/* webpackChunkName: “wqloadsh”*/‘lodash’).then(( default: _ )) => {
      return _.join([‘a’,’b’,’c’])
    } // /* webpackChunkName: “wqloadsh”*/ 魔法注释,异步模块打包出来命名成verndors~wqloadsh.js
  } // 若不做上述注释,则loadsh异步模块会被打包出来命名成0.js。复制代码

@babel/plugin-syntax-dynamic-import babel官方提供的这个插件支持魔法注释

31、SplitChunksPlugin配置参数详解

optimazation配置全部是设置同步代码分割和分组规则的。因为异步代码分割就是单独打包到一个文件。

optimazation: {
     chunks: ‘async’, // 默认async表示只对异步代码做代码分割。all表示对同步、异步都做代码分割。
     minSize: 3000, // 引入的同步模块大于3kb才做代码分割
     maxSize: 5000, // 当vendor.js大于5Kb的时候做二次分割,基本不使用这个。 
     minChunks: 2, // 当一个同步模块被多少个打包出来的chunk引用的时候才做代码分割                                                 
     maxAsyncRequests: 5, // 同时加载的模块数最多是5个。    
     maxInitialRequests: 3, // 入口文件main.js做代码分割不超过3个。
     automaticNameDelimiter: ‘~’, // 组和文件之间的连接符号
     cacheGroups: { // 缓存组(即代码分割分组)规则。
       vendors:{ // vendors组
         test: /[/]node_modules[/]/ ,  // 代码分割时,在node_modules目录下的命名为vendors组
         priority: -10 // 对同步异步代码都生效,因此上面异步模块打成单独文件时命名verndors~wqloadsh.js
         filename: ‘vendors.js’ // 一旦发现同步引入的代码从node_modules目录的,都打包到vendors.js
        } ,
    default: { // default组,所有被2个chunk引用过的模块都符合default组,都可以放到default组里。
            priority: -20, // 但是default组的priority会设置的比vendors组低,所有模块优先匹配vendors组。
       reuseExistingChunk: true, // 如果模块以及打包过,直接复用不重复打包。
       filename: ‘common.js’ // 非node_module目录下的同步代码分割到common.js组。
    }
     }
  }复制代码

32、Lazy Loading懒加载,Chunk是什么?

懒加载:通过import()异步加载函数来异步的去加载一个模块,初始不加载,执行import()函数时加载。懒加载不是webpack的概念,而是ES import()函数里面的概念,import().then() 说明import()函数返回的是Promise,如果浏览器不支持Promise则需要安装babel-polyfill。使用:

document.addEventListener(‘click’, () => {
  import(/* webpackChunkName: ‘wqlodash’ */ ‘lodash’).then( { default: _ } => {  _.join([‘aa’, ‘bb’]) })
}) // 异步模块vendors~wqloadsh.js初始不会加载,当点击页面任何位置时才回去加载
// Webpack的异步加载是基于Ajax的JSONP实现的,相比手动创建script标签后插入页面加载JS效果相同。复制代码

ES7 中async关键字写法:

async function getComponent() {
  const { default: _ } = await import(/* webpackChunkName: ‘wqlodash’*/ ’loadsh’)
  _.join([‘aa’, ‘bb’])
}复制代码

chunk是什么:所有JS打包出来的(JS、css)文件都是一个chunk。

optimazation.minChunks: 2, // 当一个同步模块被2个打包出来的chunk引用的时候才做代码分割

33、打包分析,Preloading, Prefetching

官方打包分析:

(1) webpack — profile — json > stats.json 把整个打包过程的描述输出到stats.json文件

(2) 翻墙 webpack.github.io.analyse网站:把stats.json文件上传网站实现可视化分析

assets: 打包出来的静态资源

官方推荐可视化打包工具: webpack-bundle-analyzer

Preloading,Prefetching:

为什么optimization.chunks: ‘async’ 的默认值是async,不对同步代码做分割。这是因为同步代码分割只是把一次引入的main.js代码分割成vendors.js common.js main.js多个文件同时引入,利用缓存避免重复加载vendors.js,common.js这些公共重复代码,达到提高了第二次加载页面的访问速度效果。然而缓存带来的性能提升是非常有限的,真正对页面性能做优化,是需要第一次加载页面时加载速度就是最快的,靠同步代码分割(分割出vendors.js, common.js分组)是满足不了需求的。webpack真正希望我们编写代码的方式是怎样的:

首先观察页面性能:cmd + shift + p,搜索打开Coverage面板,点击开始录制屏幕。然后刷新页面,看到

加载的main.js文件利用率只有74%(红色标注表示未执行代码,绿色标注表示已执行代码)。

性能优化:写高性能代码的时候,现在重点考虑的不是缓存这样的东西,而是代码的使用率。把页面初始不

需要加载的代码变成异步代码,首屏时间就会更短,网页访问速度就会更快。webpack希望我们多去

写异步加载代码,才能让网站加载性能真正得到提升,这也是为什么optimization.chunks: ‘async’ 的

默认值是async,而不是all。

实际应用案例:比如登录弹框,在点击登录按钮后才弹出来,就可以把弹框代码放到点击按钮当中去

import()异步加载。也许会有疑问,如果点击按钮时才去加载异步代码,交互不就会很慢吗,那么

preloading、prefetch就是用来解决这个问题的。

preloading、prefetch:在网络带宽空闲的时候,偷偷的去下载异步代码。等真正执行import()去异步加载

时,其实已经下载到浏览器缓存,这时候就是利用浏览器缓存加载。

案例代码:

document.addEventListener(‘click’, () => {
     import(/* webpackPrefetch: true */ ’./click.js’).then(({ default: func }) => {
       func() // /* webpackPrefetch: true */ 魔法注释,webpack能识别,实现异步代码prefetch
     })
  }) // 把首屏不会执行的交互代码放到了click.js文件,在使用时再异步加载。复制代码

prefetch、preload区别:prefetch等主流程加载完,有空闲再加载,更合适。 preload和主业务文件一起

加载。prefetch在某些浏览器上会有兼容问题。

总结:异步代码+preload/prefetch 是最佳方案。既能实现首屏性能最优,又能实现交互性能最优。

34、CSS文件的代码分割

webpack打包默认css直接打包到JS里(css in js):

output: {
    filename: ’[name].js’, // entry中入口文件JS打包名称走filename配置项,直接被页面引入
    chunkFilename: ‘[name].chunk.js’, // 指未被列在 entry 中,却又需要被打包出来的 chunk 文件的名称。一般来说,这个 chunk 文件指的就是要懒加载的代码。
  }

使用插件mini-css-extract-plugin(底层依赖SplitChunkPlugin)可以抽取CSS到单独文件,使用:

(1)loaders配置:

test: /.css$/, use: [
       { loader: MiniCssExtractPlugin.loader, options: { publicPath: ‘../‘}},  //最后一步不能再用css-loader
       ‘css-loader’
    ]复制代码

(2)plugins配置:

plugins: [
    new MiniCssExtractPlugin({
    filename: ‘[name].css’, // 被页面直接引用的css名称走这个配置,这个插件默认合并同步css模块。
        chunkFilename: ‘[name].chunk.css // 间接引用(main.css引入)的css名称走这个配置
    }),
    new OptimizeCssAssetsWebpackPlugin({}) // 压缩css的插件
  ]

optimization.splitChunks: {
  cacheGroups: {
    style: {
        name: ‘styles’,
        test: ‘/.css$/‘,
    chunks: ‘all’, // 将所有的css模块,包括同步代码、异步代码,都打包到styles.css一个文件。
    enforce: true // 忽略minSize、minChunks等所有配置参数
    },
    fooStyles: { // MiniCssExtractPlugin官方代码:介绍拆分不同入口JS中引用的CSS代码到不同CSS文件
      name: ‘foo’,
      test: (m,c,entry = ‘foo’) => … // entry中foo.js入口文件中引用的CSS单独打包到foo.css
    },
    barStyles: {
      name: ‘bar’,
      test: (m,c,entry = ‘bar’) => … // entry中bar.js入口文件中引用的CSS单独打包到bar.css
    },
  }
}复制代码

注意:如果在optimization.splitChunks配置了usedExports: true实现treeShaking,则必须在package.json中添加”sideEffects”: [“*.css”]使CSS不被摇树。因为import “xx.css”只引入未显示使用会被摇掉。

35、webpack与浏览器缓存(Cacheing)

performance:false // 关闭webpack性能警告提示

解决浏览器缓存问题: 浏览器非强制刷新不会重新拉取新打包出的同名资源,使用contenthash解决。

output: {
  filename: ‘[name].[contenthash].js’,
  chunkFilename: [name].[contenthash].js
}
老版本的webpack4存在问题,代码不变时,打包出来的contenthash会变,解决:
  optiomization: {
    runtimeChunk: { name: ‘runtime’ } // 把manifest关系代码提取到runtime.js
  }复制代码

原因:业务逻辑代码(main.js)和第三方库(vendors.js)是有关联的,webpack把这关联部分称之为manifest。manifest代码既存在于main.js,也存在于vendors.js,这样即使未改变代码,manifest每次打包都会有差异,同时main.js和vendors.js中的内容也跟着变了,contenthash也就变了。在旧版webpack4中需要我们手动配置抽取manifest代码,新版webpack4已默认帮我们处理。

36、Shimming(加垫片:形式包括babel-polyfill, ProvidePlugin, imports-loader, Authoring Lib等)

webpack打包是基于模块的,模块与模块之间的变量是隔开的。比如a.js引入了jQuery,那么b.js中要想使用$符号,必须自己引入jQuery。

Shimming可以理解为AOP(如过滤器),全局的更改webpack对模块的默认打包行为。

(1) ProvidePlugin:

plugins: [
     new webpack.ProvidePlugin({ // 此插件可自动帮所有模块引入使用了的库
     $: ‘jquery’, // 当webpack发现模块里面用了$符号,webpack会在模块里面自动引入jquery
     _: ‘lodash // 帮使用了_符号的模块自动引入lodash
    _join: [‘lodash’, ‘join’], // 只引入lodash模块中的join方法
     })
  ]复制代码

(2) imports-loader: 每个模块的this默认指向模块自身,imports-loader可以改变所有模块的this,如全部指向window:

{ 
      test: /.js$/, 
      use: [ { loader: ‘babel-loader’ }, 
                { loader: ‘imports-loader?this=>window’ }  // 更改webpack默认行为
       ]  
    }复制代码

37、环境变量的使用

webpack —env.production —config ./build/webpack.common.js //向配置文件传了env.production=true

webpack.common.js:
module.exports = (env) => {
  if(env && env.production) return merge(commonConfig, prodConfig)
  else return merge(commonConfig, devConfig)
}复制代码

38、Library库的打包--打包一个库

1、math.js库 
  export function add() ... mup
2、string.js库
  export function join() ... split
3、index.js
  import * as math from ‘math.js'
  import * as str from 'string.js'
  export default { math, string }
这样index.js暴露出了工具给别人使用,但是这样直接在浏览器上不能运行,需要先进行webpack打包。
webpack配置:
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  externals: ["lodash"], // 声明依赖的外部库,不打包到库里面,而是让业务代码去加载lodash,避免重复打包
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    libraryTarget: 'umd', // 支持任何模块形式的引入,如import,const xx =require,require               (['library'], function() {}) , 
    library: 'library', // 支持<script src='library.js'/> 形式引入,直接挂载library对象到                   全局对象window上
  }
}

其次,library和libraryTarget可以配合使用,libraryTarget: 'window'指定以标签引入是挂在到window对象上,如果在node中则可以设置libraryTarget: 'global'挂在到全局global对象上。

最后,修改package.json中 "main": "./dist/library.js" // 表示最终暴露给用户使用的文件
npm publish 上面工程库,用户npm install就可以安装使用。 复制代码

39、PWA的打包配置:Web技术中的一种 

实现第一次访问成功,第二次网站挂了,这时可以通过PWA缓存访问。

npm i workbox-webpack-plugin
const WorkboxPlugin = require("workbox-webpack-plugin")
new WorkBoxPlugin.generateSW({ // 只需在生产环境中使用
  clientClaim: true,
  skipWaiting: true
})
这样打包之后dist目录会多出service-worker.js(可以理解为另类缓存)、precache-manifest.js两个文件。
还需手动加入以下代码:
if('serviceWorker' in navigator) { // 如果浏览器支持ServiceWorker
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js').then().catch()
  }
}
复制代码

测试:先启动服务器,通过浏览器访问localhost:8080可以访问。然后关闭HTTP服务器,再次访问localhost:8080还是可以访问。

40、TypeScript的打包配置 

    TypeScript是前端代码约束解决方案,主要增强代码可维护性。

    TypeScript主要作用是支持对属性和参数的类型校验,可以有效避免调用函数时传参错误。      如果是一个团队协同编程,每个人代码风格不同,同一个功能写法迥然不同,维护性难以得到保证。 规范了TypeScript的语法,也支持原始的语法,通过TypeScript最大的优势是规范我们的代码,可以有效提高TypeScript编码的可维护性。

TypeScript的webpack配置:
1、npm i ts-loader typescript --save-dev
{
  test: /.tsx?$/
  use: 'ts-loader',
  exclude: /node_modules/
}

2、根目录新建TypeScript配置文件tsconfig.json:
{
  "compilerOptions": {
    "outDir": "./dist", //可不配, webpack output中已配置
    "module: "es6",
    "target": "es5", // TypeScript语法打包最终输出形式
    "allowJs": true
  }
}
我们如果引入了外部的库,不能直接利用TypeScript自动检测的特性,如果想要Typescript也能支持
这些库错误提示,就需要安装相应库的类型文件。
npm i @types/lodash --save-dev // 安装lodash的类型文件 这样支持识别loadsh里面的传参要求
npm i @types/jqueery --save-dev // 安装jquery的类型文件 这样支持识别jquery函数的传参要求 复制代码

41、WebpackDevServer实现请求转发 

    用途一:在开发环境实现跨域发送请求,底层使用http-proxy-middleware 

    用途二:开发环境后端域名和生产环境域名不同。

例如开发环境中,8080端口启动应用,
则axios.get('/react/api/header.json') 默认请求localhost:8080/react/api/header.json
实际要请求的是http://www.dell-lee.com/react/api/header.json,需要配置devServer:
devServer: {
  proxy: {
    '/react/api': 'http://www.dell-lee.com' 
     // 把请求/react/api路径下的所有请求转发到http://www.dell-lee.com下,
     // 再把拿到的内容以localhost:8080/react/api/header.json返回给ajax。
    
    '/react/api': { // 更高级的配置
    target: 'http://www.dell-lee.com',
        pathRewrite: {
        'header.json': 'demo.json', // 如果请求header.json的数据,实际去拿demo.json的数据
      },
      secure: false,// 对HTTPS生效
      changeOrigin: true, // 改变请求origin选项,突破后端对origin限制,例如反爬虫
    }
  }
}

线上代码: 不会走devServer的配置,默认请求前端所部属的服务器地址,适用于前后端部署在同一个tomcat
容器服务器里。如果前后端是分开部署的,则需要配置axios的baseUrl指向后端服务器地址。
复制代码

42、WebpackDevServer解决单页面应用路由问题

    React单页引用中这种写法: <route path="/list" component={ List } /> 

    如果直接访问localhost:8080/list.html内容会是空。 

devServer: { historyApiFallback: true } // 开发环境中,访问非index.html页面时
 更改为访问index.html页面,解决单页应用路由问题。
 这只在开发环境中配置有效,如果是在线上环境,前端无能为力,需要后端nginx去做处理。
复制代码

43、Eslint在webpack中的配置

1、Eslint基础
约束代码,统一团队编程风格。

根目录运行 npx eslint --init // 快速初始化Eslint配置文件并安装相关依赖
运行npx eslint src // 检测src目录下的代码规范
.eslintrc配置文件:
{
  "extends" : "airbnb", // 变态airbnb规则, 还可选Google...
  "parser": "babel-eslint", // 较常用,需安装 babel-eslint
  "rules" : {
    "react/prefer...": 0, // 表示不遵循这个要求
  },
  globals: {
    document: false, // 代码中不允许对全局变量document进行覆盖
  }
}

2、webpack中配置Eslint
npm i eslint-loader --save-dev // 通过esling-loader把Eslint和webpack结合起来
{
  test: /.js$/,
  use: ['babel-loader', { loader:'eslint-loader', options: { fix: true } }], 
  // loader从后往前执行,打包前检测代码规则,但这样会降低打包速度。
  // git 钩子中执行 eslint src可以提高打包速度,但是就失去了交互式提示的便捷。
}
devServer: {
  overlay: true, // 浏览器上弹层显示错误信息
}
复制代码

44、Webpack性能优化--提升webpack打包速度的方法 

 1、跟上技术的迭代(Node,npm, yarn) webpack版本升级会提升速度,webpack依赖node,升级node 会提升速度,npm升级会提升包的分析和依赖管理性能。 

2、在尽可能少的模块上应用loader: 比如使用include,exclude配置,减少loader处理的模块数。

 rules:[{
    test: /.js$/,
    include: path.resolve(__dirname, '../src'),
    exclude: /node_modules/,
    use:[{ loader: 'babel-loader'}] 
   }]
复制代码

 3、Plugin尽可能精简并确保可靠 -- 尽可能少的使用Plugin,选择官方推荐,社区认可性能好的插 件。 optimization: { mnimizer: [new OptimizeCssPlugin({})] // 压缩CSS插件,生产打包才使用,开发环境不使用 }

4、resolve参数合理配置 

 module.exports = {
   resolve: { // 
     extensions: ['.js', '.jsx'] // 当去引入一个路径下的模块时,先查找该路径下
    对应的以.js结尾的文件,没找到则找以.jsx结尾的文件
    //extensions 一般只配置.js逻辑性文件 不建议把.css .jpg文件等配置在里面,减少文件搜索
     mainFiles: ['index', 'child'] // 引入路径时默认引入路径下的index.js或child.js
     alias: {       
       child: path.resolve(__dirname, '../src/child') //别名child是src/child.js文件路径
       @: path.resolve(__dirname, '/src') // 别名@是src根目录
     }
   }
 }复制代码

 5、使用DllPugin提高打包速度 

    目标:第三方模块只打包一次。因为第三方模块代码是不会变的,webpack每次打包的时候都会去分 析并打包。实现第三方模块第一次打包的时候去分析、查找和打包,后面不再重复分析、查找和打包。

第一步:新建webpack.dll.js: 把react,react-dom,loadsh第三方模块打包到一个dll文件中。
  mode: 'production',
  entry: {
    vendors01: ['react', 'react-dom', 'lodash'] 
  },
  output: {
    filename: [name].dll.js,
    path: path.resolve(__dirname, '../dll'),
    library: '[name]' // 将打包出的文件暴露为一个全局变量vendor01供引入使用
  }
  plugins: [new webpack.DllPlugin({
    name: '[name]',
    path: path.resolve(__dirname, '../dll/[name].manefist.json) // 给打包出的第三方模块dll
包生成映射文件
  })]
  执行webpack --config webpack.dll.js 输出:
 1、vendors01.dll.js合成了多个第三方库,通过vendors01全局变量暴露。
 2、vendors01.manifest.json映射文件保存了这些第三方库和vendors01的映射关系。
  
第二步:在项目webpack.common.js中
  new AddAssetHtmlWebpackPlugin({ 
    filepath: path.resolve(__dirname, '../dll/vendors01.dll.js')
  })
  将第三方库模块dll包引入入口页HTML,引入后控制台可以访问到vendors01全局变量。
  webpack.DllReferenecePlugin({
    manifest: path.resolve(__dirname, '../dll/vendors01.manefest.json')
  })
  将第三方模块包dll的映射文件引入,当代码引入第三方模块时,先通过映射文件中的映射关系从
  全局变量vendors01中去取,没找到才去node_modules目录查找。
 
 
在大型项目中,DllPlugin和DllReferencePlugin用的还是比较多的。还会经常将第三方库分组:
  entry: {
    vendors01: ['lodash'], 
    react: ['react', 'react-dom'], 
    jquery: ['jquery']
  },
  这样在webpack.common.js中引入使用时,要为每个分组都要需要创建一AddAssetHtmlWebpackPlugin
和DllReferenecePlugin。可以写个函数,使用fs去遍历dll目录下的文件名,通过循环添加。 复制代码

 6、控制包文件大小  

    做项目打包的时候,应该让打包生成的文件大小尽可能小。有的时候经常在代码中引入一些没有使用 的模块,引入后如果没有做treeshaking,就会在打包出的代码中多出很多冗余的代码,这些代码会拖 累webpack的打包速度。所以源代码中没有用到的包不要引入,或通过treeshaking处理。这样可以控制 webpack打包大小,提高打包速度。 

 7、thread-loader, parallel-webpack, happypack多进程打包 

    webpack默认使用node单进程打包,可以使用上述工具多进程打包,具体参考官方文档API。 

 8、合理使用sourceMap:   sourceMap越详细,webpack打包越慢。 

 9、结合stats分析打包结果:   输出打包统计数据文件,再上传到线上网站分析。 

 10、开发环境内存编译 

    如在开发环境中,webpack devserver把dist输出放在内存中,读取 更快。 

 11、开发环境无用插件剔除 

    开发环境设置模式mode为development,不压缩文件,不使用线上环境才需要的插件和配置。

45、多页面打包配置

entry: { // 多个入口文件
  aa: './src/a.js', 
  bb: './src/b.js'
}
plugins:[
  new HtmlWebpakPlugin({ // 多个入口页面
    template: 'src/index.html',
    filename: 'aa.html',
    chunks: ['runtime', 'venodors', 'aa.js']  // 指定入口页需要引入的chunks
  })
  new HtmlWebpakPlugin({ // 多个入口页面
    template: 'src/index.html',
    filename: 'bb.html',
    chunks: ['runtime', 'venodors', 'bb.js']  // 指定入口页需要引入的chunks
  })
]
输出:
aa.html引入runtime.js, vendors.js, aa.js
bb.html引入runtime.js, vendors.js, bb.js
页面很多是,写个函数使用fs遍历src目录下的入口js文件,再填充entry,plugins的多页面配置。
复制代码

JohnsonGH
32 声望1 粉丝