整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

前端基础 - Webpack 基础加面试题收集

前端基础 - Webpack 基础加面试题收集

章首发在我的个人博客:http://www.brandhuang.com/article/1586446191183

webpack基础,自己配置 webpack 进行资源打包

这只是我个人学习整理的个人笔记,可以直接跳过前面去看文章中的「参考文章」

重要提示

纸上得来终觉浅,绝知此事要躬行

请一定动手敲一敲代码,看一看效果

请一定动手敲一敲代码,看一看效果

请一定动手敲一敲代码,看一看效果

Webpack介绍

一个现代 JavaScript 应用程序的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。

Webpack 仅能理解 Javascript 和 JSON 文件,需要通过 Loader 来转换

Webpack核心概念

  • Entry(入口)
  • Output(输出)
  • Loader(允许 Webpack 处理他类型的文件,并将其转换为可被您的应用程序使用并添加到依赖关系图的有效模块。)
  • Plugins(插件是 Webpack的基础,可用来处理任何 Loader 不能处理的事情)
  • Mode(配置编译环境,可选: development、production 和 none, 默认值production)
  • Browser Compatibility(浏览器兼容性,支持所有兼容 ES5 的浏览器「IE8及以下版本除外」,若要支持旧版浏览器,需要先加载一个polyfill)

Webpack 安装与项目初始化

1. 项目初始化

创建文件夹 webpack-demo,进入文件夹执行

npm init -y

从 webpack 4 版本开始,webpack-cli 分离成一个单独的模块,安装 webpack 时还需要单独安装 webpack-cli

不建议全局安装 webpack,采用本地安装的方式

npm install webpack webpack-cli --save-dev

在 webpack-demo 目录下新建 webpack.config.js

entry 配置

Simple rule: one entry point per HTML page. SPA: one entry point, MPA: multiple entry points.

一个HTML页面一个入口,单页面一个入口,多页面多个入口

官方地址:entry-context

module.exports={
    entry: './src/index.js' //webpack的默认配置
}
module.exports={
    entry: {
        app: './src/index.js' // app是输出的文件名,output中配置了filename后,这个名字无效
    } //
}
module.exports={
    entry: [
        './src/index.js',
        './src/index2.js'
    ]
}

entry 的配置可以是 字符串、数组、对象。

output 配置

const path=require('path'); // node提供
module.exports={
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'), //输出到的文件夹
        filename: 'bundle.js', // 编译后输出的文件名
        publicPath: '/' //通常是CDN地址
    }
}

Webpack 打包 js 文件

本文所使用的 webpack 版本:

webpack@4.42.1
webpack-cli@3.3.11

从 Webpack v4 开始,不引入任何配置文件的情况下也可以使用。

在 webpack-demo 下创建 src/index.js,在其中写点内容

let arr=[1,2,3]
arr.map((item)=> {console.log(item)})

执行 npx webpack --mode=development 进行打包。

执行完后,在 webpack-demo 下可看到一个 dist 文件夹,其中的 main.js 文件,即为默认打包后的文件。

查看 main.js 文件

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("let arr=[1,2,3]\narr.map((item)=> {console.log(item)})\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

代码还是箭头函数,没有被打包成低版本代码,这不是我们所需要的。此时需要使用 webpack 的 babel-loader 来将代码转换到低版本。

安装 babel-loader

npm install babel-loader --save-dev

安装 babel 依赖:推荐读下:不容错过的 Babel7 知识

npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime --save-dev

npm install @babel/runtime @babel/runtime-corejs3

配置 loader

module.exports={
    module: {
        rules:[
            {
                test: /\.js$/,
                use: ['babel-loader'],
                exclude: /node_modules/
            }
        ]
    }
}

创建 .babelrc 文件,配置 babel

// 配置babel方式一
{
    "presets": ["@babel/preset-env"],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3
            }
        ]
    ]
}

// 配置 babel 方式二:在webpack.config.js中配置

module.exports={
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ["@babel/preset-env"],
                        plugins: [
                            [
                                "@babel/plugin-transform-runtime",
                                {
                                    "corejs": 3
                                }
                            ]
                        ]
                    }
                },
                exclude: /node_modules/
            }
        ]
    }
}

loader 是从右向左(或者从下至上)执行的

比如要配置 less-loader,还需要配置 css-loader 和 style-loader,代码如下

module.exports={
    module: {
        rules: [
            {
                test: /\.(le|c)ss$/,
                use: ['style-loader', 'css-loader', 'less-loader'],
                exclude: /node_modules/
            }
        ]
    }
}

webpack 使用 loader 的方式建议阅读官方文档:Webpack Using Loaders

设置 mode

告知 webpack 使用相应的模式进行优化打包,打包出的文件有所不同。

默认值: production

可设置: none、 production 和 development。

OptionDescriptiondevelopmentSets process.env.NODE_ENV on DefinePlugin to value development . Enables NamedChunksPlugin and NamedModulesPlugin .productionSets process.env.NODE_ENV on DefinePlugin to value production . Enables FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin and TerserPlugin .noneOpts out of any default optimization options

将打包后的 js 文件自动添加到 html 中

需要用到插件 html-webpack-plugin

npm install html-webpack-plugin --save-dev

在 webpack-demo 目录下新建 public/index.html

修改 webpack.config.js

const HtmlWebpackPlugin=require("html-webpack-plugin")
module.exports={
    ...,
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html', // 打包后的文件名
        })
    ]
}

安装 cross-env,用来提供一个兼容性好的 scripts 来使用环境变量

npm install crosee-env --save-dev

具体用法,在 packge.json 中的 scripts 中添加内容

"start": "cross-env NODE_ENV=development webpack",

之前编译执行的是 npx webpack --mode=development,现在只需要执行 npm run start

如何在浏览器中实时展示效果

使用到的插件 webpack-dev-server

npm install webpack-dev-server --save-dev

修改 package.json

"start": "cross-env NODE_ENV=development webpack-dev-server",

配置 webpack-dev-server

官方文档:dev-server

// webpack.config.js
module.expots={
    ...,
    devServer: {
         port: '8080', //默认是8080
        quiet: false, //默认不启用,如果开启了,控制台不会看到除了初始启动信息外的任何console信息,包括错误提示
        inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式
        stats: 'errors-only', //终端仅打印 error
        overlay: true, //默认不启用,是否全屏显示编译的错误信息
        clientLogLevel: "silent", //日志等级
        compress: true //是否启用 gzip 压缩
    }
}

DevTool配置

方便我们在控制台看到我们在代码中的console信息或者错误信息的实际行数,方便定位问题,否则控制台会显示编译后的位置,和实际位置打不一样

官方可选参数:devtool配置

修改 webpack.config.js

module.exports={
    ...,
    devtool: 'cheap-module-eval-source-map' //开发环境下使用,线上设置为 none 或者  source-map
}

编译 less 文件

需要用到插件 less-loader、css-loader 、 style-loader、postcss-loader 和 autoprefixer,后两个是自动添加兼容性前缀。

npm install style-loader less-loader css-loader postcss-loader autoprefixer --save-dev

新建文件 src/index.less

@color: red;
body{
  background-color: @color;
}

修改 webpack.config.js

module.exports={
    ...,
    module: {
         rules:[
            ...,
            {
                test: /\.(le|c)ss$/,
                use: ['style-loader', 'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        plugins: function () {
                            return [
                                require('autoprefixer')({
                                    "overrideBrowserslist": [
                                        ">0.25%",
                                        "not dead"
                                    ]
                                })
                            ]
                        }
                    }
                }, 'less-loader'],
                exclude: /node_modules/
            }
        ]
    }
}

推荐在根目录新建 .browserslistrc 文件来配置 postcss-loader

处理图片/字体文件等

需要用到插件 url-loader 和 file-loader

url-loader 处理资源时,将配置的limit限制大小以内的资源以 DataURL 返回

官方文档:url-loader、file-loader

npm install url-loader file-loader --save-dev

配置 webpack.config.js,参考 vue-cli2.x 构建的项目配置

module.exports={
    ...,
     module: {
         rules:[
            ...,
             {
                test: /\.(woff2?|eot|ttf|otf|png|jpe?g|gif|svg)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                limit: 10240, // 10k,大于 10k 将使用 file-loader
                esModule: false, // file-loader的配置,默认使用ES Module,否则使用 CommonJS
                }
            }
         ]
     }
}

清空 dist 文件夹

每次打包时,旧的打包文件不是基本不是我们需要的,所以需要清空文件夹,懒得手动去删除文件夹,需要使用插件 clean-webpack-plugin

每次执行打包后都会清空文件夹中的内容重新生成

npm install clean-webpack-plugin -D

修改 webpack.config.js 的 plugins

const { CleanWebpackPlugin }=require('clean-webpack-plugin');
module.exports={
    ...,
    plugins: [
        ...,
        new CleanWebpackPlugin()
    ]
}

最后

一个基础的webpack就配置完了

参考文章

官方文档地址,建议随时查阅巩固:Webpack官网

祭出掘金大佬的三篇文章,从基础到进阶都有,跟着做完肯定有收获

  • 带你深度解锁Webpack系列(基础篇)
  • 带你深度解锁Webpack系列(进阶篇)
  • 带你深度解锁Webpack系列(优化篇)

一些 webpack 相关面试题

Webpack 的热更新原理

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

  1. webpack 使用 webpack-dev-server 启动一个本地服务
  2. 启动的本地服务与浏览器之间维护了一个 Websocket
  3. webpack 监听源文件的变化,当开发者保存文件时触发webpack的重新编译,编译完成后通过socket向客户端推送当前编译的hash值。
  4. 客户端通过websocket接收到推送过来的hash值。
  5. 客户端接收到 hash值 后,通过 Ajax 向服务端发送请求,服务端返回包含所有需要更新模块的的hash值构成的的一个json。
  6. 获取到 json 后,再通过 jsonp 获取到最新的模块代码。
  7. 通过 HotModulePlugin 进行更新处理,

细节参考:Webpack HMR 原理解析

webpack的构建流程

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

如何提高webpack的构建速度

  1. 多入口情况下,使用 CommonsChunkPlugin 来提取公共代码
  2. 通过 externals 配置来提取常用库
  3. 利用 DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  4. 使用 Happypack 实现多线程加速编译
  5. 使用 webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采用了多核并行压缩来提升压缩速度
  6. 使用 Tree-shaking 和 Scope Hoisting 来剔除多余代码

题目后续还会再收集添加一部分

ebpack是开发Vue.js单页面应用(SPA)最基本的工具。通过管理负责的构建步骤能够使开发工作流非常的简单,同时也能够优化应用的大小提升应用的性能。

在这篇文章我将为大家展示Webpack是如何应用在Vue app中的,包括

1. 单文件组件

2. 优化Vue项目的构建

3. 浏览器缓存管理

4. 代码分割

关于Vue-cli

如果你是使用vue-cli创建的项目整体框架,那么默认就提供了Webpack的配置文件。Webpack已经很好的集成到你的项目中了,我也没有更进一步提升的优化的建议可以提供!

那么既然提供了一个开箱即用的工具,你可能对到底是如何运行工作的不是很了解,对吗?在本篇文章我们将讨论与vue-cli提供的默认配置类似的功能。

1.单文件组件

Vue一个非常明显的特点是它使用HTML作为组件的模板。这也就必然伴随着一个一直存在的问题,不管是你将组件模板的HTML标记比较笨拙的放置于JavaScript字符串中,还是将模板和组件的定义放置于单独的文件中,管理起来都比较麻烦。

Vue提供了一个非常棒的解决方案单文件组件(Single File Components SFCs),单文件组件将模板,组件定义以及CSS都统一整齐的放置在一个.vue文件内。

<template>
 <div id="my-component">...</div>
</template>
<script>
 export default {...}
</script>
<style>
 #my-component {...}
</style>

通过Webpack的vue-loader插件,我们可以在项目中灵活的使用SFCs。这个插件会将SFCs的模板、组件定义以及样式进行拆分成块并传输给特定的Webpack loader进行后续处理,例如:script块将交给`bable-loader`处理,template快将交给Vue自己的`vue-template-loader`处理它会将模板转换并传输给`render`函数。

vue-loader最后的输出会是一个包含在Webpack bundle文件里的JavaScript模块。

一个非常典型的vue-loader配置如下:

module: {
 rules: [
 {
 test: /\.vue$/,
 loader: 'vue-loader',
 options: {
 loaders: {
 // Override the default loaders
 }
 }
 },
 ]
}

2.优化Vue项目的构建

## 只在运行时构建

如果你的Vue应用没有HTML模板,只是使用到了render函数,那么你没有必要使用到Vue的模板编译功能。那么就可以在Webpack构建是忽略这部分的代码降低打包后的文件大小。

*请记住在开发模式下单文件组件是被预编译到render函数的*

Vue提供了一个`runtime-only`的库,这个库包含了除模板编译外的所有功能,库名是`vue.runtime.js`。它的搭建比完整版本要小20KB,因此如果可以使用这个版本那么非常值得这么做。

默认情况下就是使用的runtime-only,因此每当我们在项目中使用`import vue from 'vue';`就是使用的这个版本。通过配置`alias`也可以改变这种方式:

resolve: {
 alias: {
 'vue$': 'vue/dist/vue.esm.js' // Use the full build
 }
},

## 在生产环境上剥离警告和错误信息

在生产环境上降低最终打包文件大小的另外一种方法是移除警告和错误信息。这样最终打包的文件里就不再有非必需的代码,进而提高整个文件的加载速度。

如果你去检查Vue的源码你会发现警告信息处理是根据当前环境变量`process.env.NODE_ENV`来进行判断的:

if (process.env.NODE_ENV !=='production') {
 warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm);
}

如果`process.env.NODE_ENV `设置为`production`那么这部分代码在构建的时候就会自动的被剥离。

可以使用`DefinePlugin`来设置`process.env.NODE_ENV `的值,也可以使用`UglifyJsPlugin`插件将代码进行最小化和移除不使用的代码块的处理操作。

if (process.env.NODE_ENV==='production') {
 module.exports.plugins=(module.exports.plugins || []).concat([
 new webpack.DefinePlugin({
 'process.env': {
 NODE_ENV: '"production"'
 }
 }),
 new webpack.optimize.UglifyJsPlugin()
 ])
}

3.浏览器缓存管理

用户浏览器的缓存策略是浏览器会判断网页文件是否已经在本地有未过期的副本,如果存在则浏览器会使用本地的缓存文件而不会去服务器重新下载。

如果将所有的代码打包在一个文件里面,那么任何微小的改变都意味着整个打包文件都得重新下载。理想情况下是用户尽可能的少下载,多使用本地缓存副本。那么最明智的做好就是将经常需要变动的文件与很少变动的文件做分离。

## 第三方库文件

使用*Common Chunks plugin*能够将第三方库文件(如:Vue.js库)从你的应用代码的抽出为一个独立的文件。

我们可以配置插件判断文件依赖是否是来自于`node_modules`文件夹,如果是的,那么将这些文件打包输出到一个独立的文件`vendor.js`

new webpack.optimize.CommonsChunkPlugin({
 name: 'vendor',
 minChunks: function (module) {
 return module.context && module.context.indexOf('node_modules') !==-1;
 }
})

## 文件指纹

当构建生成新的打包文件,我们怎么才能销毁浏览器的缓存或是说怎么才能使缓存失效从而从服务器加载最新的文件呢?默认情况下只有当缓存文件失效过期,或是手动清空缓存后,浏览器才会从服务器请求资源文件。当服务器表名文件已经被改变后文件将被重新下载(否则服务器会返回304 Not Modified)。

为避免不必要的请求判断,我们可以在文件发生变化时修改文件的名称这样强制浏览器重新下载。实现该功能一个简单的办法就是将“指纹”hash信息添加到文件名里,例如:

当文件内容发生变化的时候 `Common Chunks plugin`会发出生成一个“chunkhash”。Webpack在进行文件输出的时候可以使用这个hash值将它添加到输出的文件名里:

output: {
 filename: '[name].[chunkhash].js'
},

当我们如此配置后,打包生成的文件就会发生变化,类似*app.3b80b7c17398c31e4705.js*

## 自动注入打包文件

当我们安装上面提及的方法为文件添加指纹信息后,那么在每个引用文件的地方每当打包文件发生变化我们都得去更新引用信息,因为生成的文件名每次都会发生变化(hash值会改变)。

`<script src="app.3b80b7c17398c31e4705.js"></script>`,如果全部由人工手动的方式来做那么无疑这是个艰巨的任务,幸运的是我们可以使用*HTML Webpack Plugin *。这个插件可以在编译运行时将相关引用(打包生成的文件)自动注入到html文件中。

一开始我们需要将相关引用从index.html中移除

index.html

<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8">
 <title>test-6</title>
 </head>
 <body>
 <div id="app"></div>
 <!-- built files should go here, but will be auto injected -->
 </body>
</html>

*HTML Webpack Plugin*添加到配置信息

new HtmlWebpackPlugin({
 filename: 'index.html'
 template: 'index.html',
 inject: true,
 chunksSortMode: 'dependency'
}),

至此,构建生成的带有指纹信息的文件将自动注入到index.html文件中。

4.代码分割

默认情况下,Webpack将会把所有的应用代码打包到一个文件里面。但是当我们的应用有多个页面的时候将各自的代码生成到独立的文件会更加高效,当页面加载时只加载各自需要的文件。

Webpack提供了 "code splitting" 的功能可以实现此要求。

## 异步组件

与将定义对象信息放置作为第二个参数相比不同,异步组件需要使用到Promise,例如:

Vue.component('async-component', function (resolve, reject) {
 setTimeout(()=> {
 resolve({
 // Component definition including props, methods etc.
 });
 }, 1000)
})

## require

当需要加载使用异步组件的时候可以使用Webpack的require语法,这将会告诉Webpack将异步组件打包到一个独立的文件,Webpack将通过AJAX的方式加载这个文件,因此在代码里可以这样写:

Vue.component('async-component', function (resolve) {
 require(['./AsyncComponent.vue'], resolve)
});

## 延迟加载

在Vue.js应用中我们会使用*vue-router*来管理将我们的单页面应用转换为多个页面,延迟加载是使用Vue和Webpack实现代码分割的方式

const HomePage=resolve=> require(['./HomePage.vue'], resolve);
const rounter=new VueRouter({
 routes: [
 {
 path: '/',
 name: 'HomePage',
 component: HomePage
 }
 ]
})

来源:[4 Ways To Boost Your Vue.js App With Webpack](https://vuejsdevelopers.com/2017/06/18/vue-js-boost-your-app-with-webpack/?fbclid=IwAR1E-I2lTdttUGgjHAU-aVtTWoDN0qYeVtDFlKEpevbGGlqEZ4taJIGyT4c)

作者:Anthony Gore

式是Vue CLI项目中的一个重要概念,默认情况下它有三种模式:

  • developmentvue-cli-service serve 使用
  • testvue-cli-service test:unit 使用
  • productionvue-cli-service buildvue-cli-service test:e2e 使用

通过传递–mode选项标志,可以覆盖用于命令的默认模式。例如,如果要在build命令中使用开发变量:

vue-cli-service --mode development

运行 vue-cli-service 时,将从所有相应的文件加载环境变量,如果它们不包含 NODE_ENV 变量,则会相应地进行设置。

例如,NODE_ENV 将在生产模式下设置为 “production”,在测试模式下设置为 “test”,否则默认为 “development”;然后 NODE_ENV 将确定应用程序运行的主要模式-开发、生产或测试-并因此创建什么样的webpack配置。

  • NODE_ENV 设置为 “test” 时,Vue CLI会创建一个webpack配置,用于单元测试并对其进行优化,它不处理单元测试不需要的图像和其他资产。
  • NODE_ENV=development 创建一个webpack配置,该配置启用HMR,不散列资产或创建供应商包,以便在运行dev服务器时能够快速重新构建。
  • 运行 vue cli service build 时,无论要部署到什么环境,都应始终将节点环境设置为“生产”以获取可供部署的应用程序。

注意:如果 NODE_ENV 的环境中有默认值,则它在运行 vue-cli-service 命令时将删除或进行显示设置。

环境变量

环境变量我们可以从它的模式还有变量内容进行认识和了解:

我们可以通过在项目根目录中放置以下文件来指定环境变量:

.env               // 在所有情况下加载
.env.local         // 在所有情况下加载,被git忽略
.env.[mode]        // 仅以指定模式加载
.env.[mode].local  // 仅在指定模式下加载,被git忽略

一个环境文件仅包含环境变量的key=value对;

只有以开头的变量 VUE_APP_才会使用静态嵌入到客户端包中 webpack.DefinePlugin

加载的变量将对所有 vue-cli-service 命令,插件和依赖项可用。

在客户端代码中使用 Env 变量

console.log(process.env.VUE_APP_SECRET)

在构建过程中,process.env.VUE_APP_SECRET 将被相应的值替换。在的情况下 VUE_APP_SECRET=secret,将被替换 "secret"

所有已解析的env变量都将 public/index.html 在HTML插值中讨论的内部可用。

局部变量

默认情况下,.gitignore 将忽略本地env文件。

有时候我们有不需要提交到代码块的env变量时,而且项目托管也在公共存储库中时,这种情况下,我们应该改用 .env.local 文件。.local 也可以附加到特定于模式的env文件中。