高级前端-webpack

什么是webpack:webpack is a module bundler

Tapable是webpack的底层依赖

loader让webpack可以处理除了js和json文件以外的别的类型的文件

module.rules.{use: '', test: ''} 目前用的是chainWebpack可以链式调用loader和plugin,并且可以修改对应loader或者插件的属性

webpack的主要配置

  1. Entry
  2. Output
  3. loader
  4. plugin

webpack代码分离

  1. 多入口
  2. splitChunks
  3. 动态导入

为什么要用webpack

从Entry文件开始,加载并处理所有的文件,最终转为bundle.js文件

webpack怎么用

1. 全局安装webpack + webpack-cli

2. 全局直接使用webpack xxx.js

3. 项目中安装webpack + webpack-cli, 使用npx webpack,默认入口文件./index.js,默认配置文件:webpack.config.js

4. 可以自己创建一个webpack.config.js用来修改webpack的配置

5. 也可以重定义webpack.config.js的名字 => vue.config.js,但是打包命令得这么写:npx webpack --config vue.config.js

6. 为了不用每次都去调用npx,可以使用npm script去做优化,在package.json中配置相关script,例如:

script: {
    build: 'webpack'
}

使用npm run build去代替npx webpack; script的执行会优先从当前所在项目的node_module中去找相关指令

7. webpack-cli的作用是为了可以在命令行运行webpack,不装的话不能运行

8. webpack是基于node.js去编写的,因为打包的功能需要去遍历项目中用到的所有文件,需要用到一些原生功能,js是做不到的,所以用node.js是很容易实现的

打包结果命令行输出分析

1. Hash:本次打包唯一的hash值

2. asset: 打包出的文件名

3. chucks: 打包的文件的唯一值得集合

4. chuck names: 打包文件的名字的集合

5. chuck names怎么来的:main

6. entrypoint: 入口文件

7. webpack.config.js的默认mode是production,也可以设置成development,如果是production会默认压缩代码,development不会压缩代码

loader

模块不一定是js文件,也有可能是css文件,图片文件,webpack默认只知道打包js模块文件,其他类型的文件的打包就需要借助loader

module: {
    rules: [{
        test: /\.jpg$/
        use: {
            loader: 'file-loader'
        }
    }]
}

1. loader的作用过程,当webpack遇到非js模块的时候,去找可以识别当前模块的loader,对应的loader会把对应的module文件编译成对应的文件放到dist目录下面

2. vue项目中动态引入本地图片,需要require或者import,应该就是loader不会再编译???这边有待研究

3. vue里面打包分两种,一种会被loader处理,public目录中的则直接拷贝一份放到dist目录中

4. loader提供了options提供修改打包编译的属性,例如可以修改打包后的文件名,文件输出位置等

5. url-loader和file-loader的区别

url把图片变成base64的路径,放到bundle.js中,file-loader把图片打包到dist目录下, url-loader提供了limit的option,如果小于limit默认使用url-loader,大于limit使用file-loader的方式打包

6. loader的执行顺序是从后往前执行

7. 样式加载相关loader: postcss-loader(auto-prefix), css-loader(用来解析模块化css文件的loader), style-loader(把编译后的css文件,添加到header里面去)

plugin(可以在webpack打包到某个时刻的时候,帮我们干一些事情)

1. 帮助友好的进行打包,没有plugin, index.html不会被打包到dist中

2. 使用html-webpack-plugin,在webpack打包结束后,自动生成一个index.html文件,并把打包生成的js自动引入到index.html中

3. clean-webpack-plugin,每次打包,删除dist目录下的文件

SourceMap(源代码和目标代码的映射)

1. 报错提示可以定位到编译前原代码

2. devtool:变成source-map

3. source-map打包后会多出来一个.map.js文件

4. inline-source-map: map.js文件不见了,map文件通过dataurl(base64格式)的方式写入到.js文件中

5. cheap-inline-source-map: inline会精确到哪一行的哪一列出错,cheap之后,只提示哪一行出错,这样加大了编译后文件的运行性能

6. cheap只管业务代码,cheap-module-source-map,module的话,不光对业务代码作用,并且对第三方代码都产生作用

7. eval,是打包速度最快的,使用eval运行js代码,并且多了一个sourceUrl的属性指向源文件,不会有dataurl,也不会有map.js

建议:

开发环境使用:cheap-module-eval-source-map

线上环境使用:一般不需要设置source-map,但是为了方便线上问题排查,通常推荐使用cheap-module-source-map

webpack-dev-server

1. webpack-dev-server监听到数据代码的改变,就会重新编译,网页会自动刷新,这样原来呈现的网页内容就不见了

2. 为了解决以上自动刷新的问题,于是出现了HMR,Hot Module Replacement plugin,只替换修改的内容

devServer: {
    hot: true,
    hotOnly: true // 热跟新加载失败的时候,也不刷新页面
}

3. 不使用HMR的时候,修改了某个模块的代码,会把所有的代码刷新

Babel (高版本的js转低版本的js,polyfill)

1. babel-loader只是一个webpack和babel之间通信的桥梁

2. babel-loader的options中使用

rules: [
    test: '\.js$',
    exclude: 'node_module',
    use: 'babel-loader', // webpack 与 babel通信的桥梁
    options: {
        // 解决业务代码的配置,preset中文名预置,大概意思就是制定对应的语法解析器
        presets: [
            '@babel/preset-env', // 把高版本的js语法转成低版本,但是实现不了新的语法糖,需要使用polyfill
            {
                // target的作用是制定目标环境进行babel
                'target': {
                    'chrome': '64'
                },
                'useBuiltIns': 'usage' // 如果不设置这个属性,打包出来的js里面会编译出来所有的polyfill,添加了这个属性之后,编译出来的js里面只有用到的polyfill才会被编译
            }
        ],
        // plugin用来实现除了语法解析的一些别的功能,例如‘transform-remove-console’
        plugins: [‘transform-remove-console’]
    }
]

// 对应需要polyfill的js文件中,提前import 'babel-polyfill'

Tree Shaking (只支持es module)

1. 场景:a.js 导入了b.js里面的一个方法,但是webpack编译之后会把b.js里面的所有方法打包编译,有没有什么办法只打包编译import的那一部分代码呢?

2. Tree Shaking可以很好地解决1中的问题,那么如何使用tree shaking呢?

// mode是development的情况下,webpack.config.js
module.export = {
    rules: ...
    plugins: ...
    optimization: {
        usedExported: true
    }
}

// Tip: develompent的mode下,即使用了tree shaking,也不会自动把不用的代码去除掉。。。尴尬,
// 那好像并没什么用,只是在打包编译出来的js文件里面有个注释告诉你,用到了这个模块的哪些方法。。。
// 这么看来,开发环境没必要使用tree shaking


// mode: 'production' 模式下自动开启tree shaking,哈哈哈,如此尴尬

// dev和prod两种mode下,都要在package.json文件中设置
{
    sideEffects: false // 这个属性可以用来指定哪些文件不适用tree shaking
}

webpack和code spliting

1. 他们之间的关系是什么?

场景:a.js中import _ from 'lodash',a.js[1M], b.js[1M],那么打包出来的main.js就是2M

如何实现分割呢,那么可以通过webpack的多入口方式,把lodash和main分开

2. webpack中自带了一个插件:splitChunkPlugin

optimization: {
    splitChunks: {
        chunks: 'all' // async:只对异步代码分割,all:同步异步都分割,initial:同步代码分割
        cacheGroups: { // 以上条件都成立后,先不打包,再按照cacheGroups里面的规则打包
            vendors: {
              name: 'chunk-vendors',
              test: /[\\/]node_modules[\\/]/,
              priority: -10,
            }, 
        }
    }
}

webpack有两种代码分割方式:1. 同步导入,2. 异步导入

1. 同步导入,使用webpack自带插件SplitChunkPlugin

2. 异步导入:import('lodash').then(({default: _}) => {}) 不需要使用插件,自动进行代码分割,

3. lazy load: 懒加载正是使用了异步导入实现,vue的路由懒加载,由此实现

打包分析,Preloading与Prefetching (重点,重点,重点,需要反思,需要生产实践!!!)

打包分析

[参考](https://github.com/webpack/analyse): webpack --profile --json > stats.json

生产的js文件,到这个(http://webpack.github.io/analyse/)网站去做分析,形成可视化界面

Preloading

小技能:浏览器console内ctrl + p, 输入> coverage,可以看到js文件的利用率

场景:页面点击登录,跳出弹出框

方案1:点击登录后,加载登录弹出框相关的js

方案2:页面加载完之后,预加载登录模态框相关的js

如何实现方案2:webpack的prefetching,使用异步import的魔法注释

prefetch和preload的区别

1. prefetch会在主js加载完成之后,再加载

2. preload会和主js一同加载

3. 推荐使用prefetch

CSS文件代码的分割

mini-css-extract-plugin

Cache

webpack配置output的时候,对打包生成的文件添加[hash]

Shimming

Shimming是什么?Shimming是垫片的意思

场景1:使用的第三方库,第三方库依赖jquery,但是第三方库没有引入jquery,这个时候我们就需要垫片,在第三方库中自动导入jquery

场景2:es6的各个模块中的this不指向window,指向该模块,那么如何让模块中的this指向window了

怎么用?

使用webpack的ProvidePlugin

实战:webpack打包library,并且发布到npm上

const path = require('path')

module.exports = {

  mode: 'production',

  entry: './src/index.js',
  external: ['lodash'], // 打包的library内部用到了lodash,但是不希望打包的时候,把lodash的文件打包进来,可以这么设置,但是使用import libary from 'library'之前,需要先import lodash from 'lodash'
  output: {

    path: path.resolve(__dirname, 'dist'),

    filename: 'floatcalc.js',

    library: 'floatcalc', // export出來的对象指向到全局this上

    libraryTarget: 'umd' // 支持使用es6 module, AMD, CMD模式

  }

}

发布到npm上:

1. npm adduser

2. npm publish

期间遇到两个问题,问题和解决方案可参考:https://www.cnblogs.com/chengzp/p/7757839.html

PWA? 页面缓存技术?即使服务器断开,也能访问到缓存html的解决方案?

参考workbox-webpack-plugin

原理是一个Service Worker,它本质上是一个与主浏览器线程分开运行的 JavaScript 文件,可以拦截网络请求、缓存资源或从缓存中检索资源、传递推送消息

service worker是web worker的一种实现

webpack性能优化方案

1. 在可以的情况下,尽量升级node, npm, yarn

2. 利用include和exclude尽量缩小loader加载文件的范围

3. plugin的使用要谨慎,优先使用官网推荐的plugin

4. 合理使用webpack的resolve属性,配置extensions,mainFiles,alias

5. dllplugin + dllreferenceplugin让第三方模块只打包一次

6. 多进程打包

7. 合理使用sourcemap

8. 多页面打包

手写loader

// customLoader.js
const { cssSourceMap } = require("../vue-loader.conf")

module.exports = function(source) {
  // source 是传进来的文件
  // loader可以用来对匹配到的文件进行对应的处理
  // loader可以使用options,如何获取options
  /**
   * options: {
   *   name: 'lee'
   * }
   */
  // console.log(this.query) // {name: 'lee'}
  // return source.replace('dell', this.query.name)

  // const result = source.replace('dell', this.query.name)
  // this.callback(null, result) // 等价于return

  // 异步处理
  const callback = this.async();
  setTimeout(() => {
    const result = source.replace('dell', this.query.name)
    callback(null, result)
  }, 1000);
}

// this.callback(
//   err: Error | null,
//   content: string | Buffer,
//   sourceMap?:SourcecssSourceMap,
//   meta?: any
// )

常用业务场景:

对所有的function包一层try catch

国际化实现,{ {title}}  => source.replace('{ {title}}', '中文标题')

手写plugin(发布订阅设计模式)

loader对特殊文件进行编译

plugin在打包的某一时刻帮助我们做一些事情

// copyright-webpack-plugin.js
// 插件实现效果:打包完成(这一句确定了编译的执行的钩子函数),在dist目录下生成一个copyright文件(这一句确定了,我们要在该钩子函数中干什么事情)
class CopyrightWebpackPlugin {
  constructor(options) {
    console.log('插件被使用了');
    console.log('options=', options)
  }

  // compiler理解为webpack的一个实例
  apply(compiler) {
    // 同步钩子
    compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
      console.log(compilation)
    })

    // 代码打包到dist目录之前, emit是个异步钩子
    compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
      debugger;
      console.log('放到dist目录之前')
      console.log(compilation.assets)
      compilation.assets['copyright.json'] = {
        source: function() {
          return 'copyright by andyhuang'
        },
        size: function() {
          return 21
        }
      }
      cb()
    })
  }
}

module.exports = CopyrightWebpackPlugin;

 小技巧node调试:"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js", 

Bundler源码编写

1. 通过入口文件,递归进行模块分析

2. 模块分析生成dependency graph依赖图谱

3. 通过依赖图谱,生成浏览器可以运行的code

猜你喜欢

转载自blog.csdn.net/qq_14855277/article/details/116063771