前端工程化之webpack

1、你知道webpack的作用是什么吗

1)模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。

2)编译兼容。在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpackLoader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。

3)能力扩展。通过webpackPlugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。

2、webpack打包的过程(必考)

构建流程

webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

1、初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;

2、开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;

3、确定入口:根据配置中的 entry 找出所有的入口文件

4、编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

5、完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

6、输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;

7、输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。 在以上过程中,webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 webpack 提供的 API 改变 webpack 的运行结果。

3、有哪些常见的Loader?你用过哪些Loader?

1.raw-loader:加载文件原始内容(utf-8)

 

2.file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)

 

3.source-map-loader:加载额外的 Source Map 文件,以方便断点调试

 

4.svg-inline-loader:将压缩后的 SVG 内容注入代码中

 

5.image-loader:加载并且压缩图片文件

 

6.json-loader 加载 JSON 文件(默认包含)

 

7.babel-loader:把 ES6 转换成 ES5

 

8.sass-loader:将SCSS/SASS代码转换成CSS

 

9.eslint-loader:通过 ESLint 检查 JavaScript 代码

 

10.vue-loader:加载 Vue.js 单文件组件

 

11.cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里

 

 

4、有哪些常见的Plugin?你用过哪些Plugin?

(这大兄弟好像听上瘾了,继续开启常规操作)

 

define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)

 

ignore-plugin:忽略部分文件

 

web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用

 

terser-webpack-plugin: 支持压缩 ES6 (Webpack4)

 

webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度

 

mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载

 

clean-webpack-plugin: 目录清理

 

speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)

 

webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

5、是否写过Loader?简单描述一下编写loader的思路?

从上面的打包代码我们其实可以知道,Webpack最后打包出来的成果是一份Javascript代码,实际上在Webpack内部默认也只能够处理JS模块代码,在打包过程中,会默认把所有遇到的文件都当作 JavaScript代码进行解析,因此当项目存在非JS类型文件时,我们需要先对其进行必要的转换,才能继续执行打包任务,这也是Loader机制存在的意义。

Loader的配置使用我们应该已经非常的熟悉:

// webpack.config.js
module.exports = {
  // ...other config
  module: {
    rules: [
      {
        test: /^your-regExp$/,
        use: [{ loader: "loader-name-A" }, { loader: "loader-name-B" }],
      },
    ],
  },
};

/ webpack

通过配置可以看出,针对每个文件类型,loader是支持以数组的形式配置多个的,因此当Webpack在转换该文件类型的时候,会按顺序链式调用每一个loader,前一个loader返回的内容会作为下一个loader的入参。因此loader的开发需要遵循一些规范,比如返回值必须是标准的JS代码字符串,以保证下一个loader能够正常工作,同时在开发上需要严格遵循“单一职责”,只关心loader的输出以及对应的输出。

loader函数中的this上下文由webpack提供,可以通过this对象提供的相关属性,获取当前loader需要的各种信息数据,事实上,这个this指向了一个叫loaderContextloader-runner特有对象。有兴趣的小伙伴可以自行阅读源码。

module.expor

module.exports = function (source) {
  const content = doSomeThing2JsString(source);
  // 如果 loader 配置了 options 对象,那么this.query将指向 options
  const options = this.query;
  // 可以用作解析其他模块路径的上下文
  console.log("this.context");
  /*
   * * this.callback 参数:
   * * error:Error | null,当 loader 出错时向外抛出一个 error
   * * content:String | Buffer,经过 loader 编译后需要导出的内容
   * * sourceMap:为方便调试生成的编译后内容的 source map
   * * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
   * */
  this.callback(null, content); // or return content;
};

ts = function (sourceoader 配置了 options 对象,那

6、是否写过Plugin?简单描述一下编写plugin的思路?

如果说Loader负责文件转换,那么Plugin便是负责功能扩展。LoaderPlugin作为Webpack的两个重要组成部分,承担着两部分不同的职责。

上文已经说过,webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务,从而实现自己想要的功能。

既然基于发布订阅模式,那么知道Webpack到底提供了哪些事件钩子供插件开发者使用是非常重要的,上文提到过compilercompilationWebpack两个非常核心的对象,其中compiler暴露了和 Webpack整个生命周期相关的钩子(compiler-hooks[2]),而compilation则暴露了与模块和依赖有关的粒度更小的事件钩子(Compilation Hooks[3])。

Webpack的事件机制基于webpack自己实现的一套Tapable事件流方案

// Tapab = r

// Tapable的简单使用
const { SyncHook } = require("tapable");
class Car {
  constructor() {
    // 在this.hooks中定义所有的钩子事件
    this.hooks = {
      accelerate: new SyncHook(["newSpeed"]),
      brake: new SyncHook(),
      calculateRoutes: new AsyncParallelHook([
        "source",
        "target",
        "routesList",
      ]),
    };
  } /* ... */
}
const myCar = new Car(); // 通过调用tap方法即可增加一个消费者,订阅对应的钩子事件了
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());

  construc

Plugin的开发和开发Loader一样,需要遵循一些开发上的规范和原则:

插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例;

传给每个插件的 compiler 和 compilation 对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件;

异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住;

了解了以上这些内容,想要开发一个 Webpack Plugin,其实也并不困难。

class MyPlugin {

  apply(compile

class MyPlugin {
  apply(compiler) {
    // 找到合适的事件钩子,实现自己的插件功能
    compiler.hooks.emit.tap("MyPlugin", (compilation) => {
      // compilation: 当前打包构建流程的上下文
      console.log(compilation); // do something...
    });
  }
}

的事件钩子,实现自己的插件

7、webpack中loader和plugins的区别

Loader:

Loader让webpack能够处理不同的文件。loader可以将所有类型的文件转换为webpack能够处理的有效模块,然后利用webpack的打包能力,对他们进行处理。本质上,webpack loader将所有类型的文件,转换为应用程序的依赖图可以直接引用的模块。例如:loader可以将sass,less文件的写法转换成css,而不在使用其他转换工具。可以将ES6或者ES7的代码,转换成大多数浏览器兼容的JS代码。可以将React中的JSX转换成JavaScript代码。

Plugins:

loader被用于转换某些类型的模块,而插件则可以用于执行广泛的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
  想要使用一个插件,你只需要 require()它,然后将它添加到plugins数组中。多数插件可以通过选项自定义。你也可以在一个配置中因为不同目的而多次使用同一个插件,这时需要通过使用new操作符来创建它的一个实例。例如js压缩、html打包打包HTML,要将 HTML 文件从 src 目录下打包到 dist 目录下,需要借助 html-webpack-plugin 插件。该插件需要安装。

 

8、source map是什么?生产环境怎么用?

source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

 

map文件只要不打开开发者工具,浏览器是不会加载的。

 

线上环境一般有三种处理方案:

 

hidden-source-map:借助第三方错误监控平台 Sentry 使用

 

nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高

 

sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)

 

注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

 

9、webpack打包如何减少体积

①去除不必要的插件

②提取第三方库,使用cdn在index.html中外部引入,cdn+webpakc的externals

③Gzip加速

④代码压缩

⑤异步加载模块

 

 

10、加快webpack打包速度

①通过babel-loader的cache配置来缓存babel的编译结果。

②推荐采用 webpack-parallel-uglify-plugin 插件,她可以并行运行 UglifyJS 插件,更加充分而合理的使用 CPU 资源,这可以大大减少的构建时间

③Happypack 的处理思路是:将原有的 webpack 对 loader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建;原本的流程保持不变,这样可以在不修改原有配置的基础上,来完成对编译过程的优化,

11、如何优化webpack构建的性能

一、减少代码体积 1.使用CommonsChunksPlugin 提取多个chunk之间的通用模块,减少总体代码体积

 2.把部分依赖转移到CDN上,避免每次编译过程都由Webpack处理

 3.对一些组件库采用按需加载,避免无用的代码

二、减少目录检索范围

 ·在使用loader的时候,通过制定exclude和include选项,减少loader遍历的目录范围,从而加快webpack编译速度

 

三、减少检索路经:resolve.alias可以配置webpack模块解析的别名,对于比较深的解析路经,可以对其配置alias

 

四、我们把开发中的所有资源(图片,js、css文件)都看成模块,通过loader和plugins来对资源进行处理,打包成符合生产环节部署的前端资源。

 

 

 

12、文件监听原理呢?

在发现源码发生变化时,自动重新构建出新的输出文件。

 

Webpack开启监听模式,有两种方式:

 

启动 webpack 命令时,带上 --watch 参数

在配置 webpack.config.js 中设置 watch:true

缺点:每次需要手动刷新浏览器

 

原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。

 

module.export = {

    // 默认false,也就是不开启

    watch: true,

    // 只有开启监听模式时,watchOptions才有意义

    watchOptions: {

        // 默认为空,不监听的文件或者文件夹,支持正则匹配

        ignored: /node_modules/,

        // 监听到变化发生后会等300ms再去执行,默认300ms

        aggregateTimeout:300,

        // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次

        poll:1000

    }

}

复制代码

13、说一下 Webpack 的热更新原理吧

(敲黑板,这道题必考)

 

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

 

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

 

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。

 

(面试官:不错不错,小伙子表达能力不错)

 

(基操,勿6)

14、如何对bundle体积进行监控和分析?

VSCode 中有一个插件 Import Cost 可以帮助我们对引入模块的大小进行实时监测,还可以使用 webpack-bundle-analyzer 生成 bundle 的模块组成图,显示所占体积。

 

bundlesize 工具包可以进行自动化资源体积监控。

15、webpack:bundle,chunk,module:

bundle是由webpack打包出来的文件,chunk是指webpack在进行模块依赖分析的时候,代码分割出来的代码块,module是开发中的单个模块

 

16、使用webpack开发时,你用过哪些可以提高效率的插件?

(这道题还蛮注重实际,用户的体验还是要从小抓起的)

 

webpack-dashboard:可以更友好的展示相关打包信息。

 

webpack-merge:提取公共配置,减少重复配置代码

 

speed-measure-webpack-plugin:简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。

 

size-plugin:监控资源体积变化,尽早发现问题

 

HotModuleReplacementPlugin:模块热替换

 

 

17、文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

 

Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改

 

Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash

 

Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

 

JS的文件指纹设置

 

设置 output 的 filename,用 chunkhash。

 

module.exports = {

    entry: {

        app: './scr/app.js',

        search: './src/search.js'

    },

    output: {

        filename: '[name][chunkhash:8].js',

        path:__dirname + '/dist'

    }

}

复制代码

CSS的文件指纹设置

 

设置 MiniCssExtractPlugin 的 filename,使用 contenthash。

 

module.exports = {

    entry: {

        app: './scr/app.js',

        search: './src/search.js'

    },

    output: {

        filename: '[name][chunkhash:8].js',

        path:__dirname + '/dist'

    },

    plugins:[

        new MiniCssExtractPlugin({

            filename: `[name][contenthash:8].css`

        })

    ]

}

复制代码

图片的文件指纹设置

 

设置file-loader的name,使用hash。

 

占位符名称及含义

 

ext     资源后缀名

name    文件名称

path    文件的相对路径

folder  文件所在的文件夹

contenthash   文件的内容hash,默认是md5生成

hash         文件内容的hash,默认是md5生成

emoji        一个随机的指代文件内容的emoj

const path = require('path');

 

module.exports = {

    entry: './src/index.js',

    output: {

        filename:'bundle.js',

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

    },

    module:{

        rules:[{

            test:/\.(png|svg|jpg|gif)$/,

            use:[{

                loader:'file-loader',

                options:{

                    name:'img/[name][hash:8].[ext]'

                }

            }]

        }]

    }

}

复制代码

18、在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作?

可以使用 enforce 强制执行 loader 的作用顺序,pre 代表在所有正常 loader 之前执行,post 是所有 loader 之后执行。(inline 官方不推荐使用)

 

 

19、你刚才也提到了代码分割,那代码分割的本质是什么?有什么意义呢?

代码分割的本质其实就是在源代码直接上线和打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。

 

「用可接受的服务器性能压力增加来换取更好的用户体验。」

 

源代码直接上线:虽然过程可控,但是http请求多,性能开销大。

 

打包成唯一脚本:一把梭完自己爽,服务器压力小,但是页面空白期长,用户体验不好。

 

(Easy peezy right)

 

20、聊一聊Babel原理吧

大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:

 

解析:将代码转换成 AST

词法分析:将代码(字符串)分割为token流,即语法单元成的数组

语法分析:分析token流(上面生成的数组)并生成 AST

转换:访问 AST 的节点进行变换操作生产新的 AST

Taro就是利用 babel 完成的小程序语法转换

生成:以新的 AST 为基础生成代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值