webpack 学习(系统掌握重点知识)

337 阅读6分钟

声明

文章主要用通俗易懂的语言来解释概念和一些重点知识,方便复习,不会太详细说明细节操作。

概念

一个打包器,识别import和export,根据依赖关系把引入的js按顺序打包,最后只输出一个js文件。

为什么要学习webpack(学某样东西总要有个理由)?

在远古时代原始时代开发的时候我们把所有js代码写在script标签里面。

<script>
    /*非常长的代码*/
</script>            

但慢慢会发现代码非常长,不好维护,于是把一些代码抽出来放在一个js文件里,代码就变成了

<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
<script src="d.js"></script>
<script src="e.js"></script>
<script src="f.js"></script>
<script src="g.js"></script>
<script src="h.js"></script>
<script>
    /*一些代码*/
</script>            

于是诞生了新的问题,一次要请求多个js,增加了服务器负担,而且js的引入要按顺序

为了解决这些边角问题,让我们只需专注开发,webpack诞生了。

一步一步根据文档学习(建议读官网的,中文文档坑太多了,不会英语的直接点翻译就好)

webpack.js.org/guides/gett…

入门

项目

webpack-demo
  |- package.json
 |- webpack.config.js
  |- /dist
    |- index.html
  |- /src
    |- index.js

package.json

{
   "scripts": {
    "build": "webpack"
   }
 }

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

执行命令

$ npx webpack --config webpack.config.js
[webpack-cli] Compilation finished
asset main.js 69.3 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 1000 bytes 5 modules
cacheable modules 530 KiB
  ./src/index.js 257 bytes [built] [code generated]/*重点*/
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]/*重点*/
webpack 5.4.0 compiled successfully in 1934 ms
提示

利用npm run build 把index.js里面的js代码输出到dist/main.js。

资产管理

loader是什么?

www.webpackjs.com/concepts/lo…

开发

使用 source map(一般用来调试看源码 )

webpack.config.js

const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
  devtool: 'inline-source-map',
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };

代码分割(重点)

防止重复

假设a,b.js都引入了x模块,打包的时候代码就会重复,因为X模块在a和b都引入了,这个时候使用SplitChunksPlugin就可以把共同的依赖提取到一个现有的条目块或一个全新的块。

webpack.config.js

const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      another: './src/another-module.js',
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
   optimization: {
     splitChunks: {
       chunks: 'all',
     },
   },
  };

动态导入

利用import('xxx).then()的异步导入。

function getComponent() {
   const element = document.createElement('div');
     return import('lodash')
    .then(({ default: _ }) => {
      const element = document.createElement('div');

      element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      return element;
    })
    .catch((error) => 'An error occurred while loading the component');
 }
 getComponent().then((component) => {
  document.body.appendChild(component);
});

预取/预加载模块

预加载prefetch:会在使用之前,提前加载js文件,等其他资源加载完毕,浏览器空闲了,再偷偷加载资源,兼容性比较差,慎用。

正常加载可以认为是并行加载(同一时间加载多个文件)

import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

这将导致将其附加在页面的开头,这将指示浏览器在空闲时间预取login-modal-chunk.js文件。

快取

用webpack打包之后会生成一个dist,把dist部署就可以让客服端(通常是浏览器)访问,但这一步会比较浪费资源,因为这一步客服端会拿新生成的dist下面的js文件和缓存里的js文件作对比,如果文件名不一样,就会重新去获取资源,然而我们有时候只修改一行代码,dist下的js文件名就全部都不一样,客户端就会再一次去请求全部资源,造成了资源浪费。快取要做的就是把更新频率低和更新频率高的代码分隔开,输出多个js文件,这样即使频繁修改代码,也只是更新有改动的小部分js文件,节省了很多资源。

Library

概念:自己写模块函数,然后利用webpack打包输出一个库。

举个栗子:

src/ref.json
[
  {
    "num": 1,
    "word": "One"
  },
  {
    "num": 2,
    "word": "Two"
  },
  {
    "num": 3,
    "word": "Three"
  },
  {
    "num": 4,
    "word": "Four"
  },
  {
    "num": 5,
    "word": "Five"
  },
  {
    "num": 0,
    "word": "Zero"
  }
]
src/index.js
import _ from 'lodash';
import numRef from './ref.json';

export function numToWord(num) {
  return _.reduce(numRef, (accum, ref) => {
    return ref.num === num ? ref.word : accum;
  }, '');
};

export function wordToNum(word) {
  return _.reduce(numRef, (accum, ref) => {
    return ref.word === word && word.toLowerCase() ? ref.num : accum;
  }, -1);
};
webpack.config.js
  var path = require('path');
  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
      library: 'webpackNumbers'//暴露变量
    },
    externals: {
      lodash: {
        commonjs: 'lodash',
        commonjs2: 'lodash',
        amd: 'lodash',
        root: '_'
      }
    }
  };

摇树

概念:摇树,顾名思义,一个树上长满了绿叶(有用的代码),把灰色的叶子(没用的代码)摇下来。

具体看:webpack.js.org/guides/tree…

将文件标记为无副作用(只是标记还没删除)

就是利用import和export识别出那些不依赖第三方库的模块,然后标记。(被标记的模块要是没被引用到,在后面就可以安全被删除.)

操作:在package.json中

{
  "name": "your-project",
  "sideEffects": false
}

如果一些代码依赖第三方库,则提供一个数组,数组里面的文件不会被识别

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js","*.css"]
}

最后是删除代码:

只需要在webpack中配上这个属性就可以了
mode: 'production'

生产环境构建

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置.

白话:就是生产环境配置一个webpack,开发环境配置一个webpack,然后生产和开发环境都要用的配置又一个webpack(公共),利用webpack-merge把公共的webpack合并到这两个环境的webpack,最后在npm配两个命令,一个是执行生产环境webapck,另一个是执行开发环境的webapck。具体操作看下面。

项目

  webpack-demo
  |- package.json
 |- webpack.common.js
 |- webpack.dev.js
 |- webpack.prod.js
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules

webpack.common.js

 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   entry: {
     app: './src/index.js',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Production',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };

webpack.dev.js

 const { merge } = require('webpack-merge');//用来合并两个webpack文件
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   mode: 'development',
   devtool: 'inline-source-map',
   devServer: {
     contentBase: './dist',
   },
 });

webpack.prod.js

 const { merge } = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   mode: 'production',
 });

package.json

{
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "src/index.js",
    "scripts": {
     "start": "webpack serve --open --config webpack.dev.js",
     "build": "webpack --config webpack.prod.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
  }

源映射(读源码会用到)

可以映射源码,下面是具体配置。

webpack.prod.js

  const { merge } = require('webpack-merge');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
    mode: 'production',
   devtool: 'source-map',
  });

懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

直接举例子:

   button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
     const print = module.default;

     print();
   });

打包之后:

...
          Asset       Size  Chunks                    Chunk Names
print.bundle.js  417 bytes       0  [emitted]         print
index.bundle.js     548 kB       1  [emitted]  [big]  index
     index.html  189 bytes          [emitted]
...

也就是说只有在点按钮的时候就会发请求加载print.bundle.js并执行print的代码。

说多一个预加载,就这个例子来说预加载就是浏览器在空闲的时候会去加载print,在按钮被点击执行print代码的时候直接就执行不用加载。具体操作:

import(/* webpackChunkName: "print",webpackPrefetch:true */ './print')

在注释里面加webpackPrefetch:true等于告诉浏览器在空闲时间加载print。

以上就是webapck的主要知识了,其他想到再补充。