资讯专栏INFORMATION COLUMN

带你了解webpack

senntyou / 989人阅读

摘要:根据依赖关系,按照配置文件把模块函数分组打包成若干个。会随着自身的的修改,而发生变化。只需要在命令行运行时带上参数就搞定一些插件的废除和替换废弃了顶替者用属性变化压缩优化代码分割,下面详解还有一些新的插件,。

1. 前端工程化项目打包历史

前端工程化之前的时代略过

1. 半自动执行脚本来压缩合并文件

自从xmlhttprequest被挖掘出来,网页能够和服务端通讯,js能做的事越来越多,文件体积越来越大,互相引用原来越多。
然而网速只有几兆的带宽。于是想到,要把js文件狠狠的压缩,能合并的就给合并起来

主要有的js压缩工具:

JSMin 使用简单、灵活,对不同语言、环境支持好。一般配合不同的环境、语言用命令行执行压缩
YUI Compressor 雅虎推出。有JAVA版本和.NET版本,需两者环境配合
UglifyJS 基于nodejs,压缩策略较为安全,所以不会对源代码进行大幅度的改造。
Closure Compiler 谷歌出品

在我理解,压缩主要做了局部变量命名简化、空格/换行/注释消除、自动优化可简化的语法等工作。

使用压缩工具用es6语法写的js在压缩测试比较中:

UglifyJS压缩率高,可以自动格式化、优化代码。所以普及率高。现在也是主流的工具

YUI compressor 好处就是压缩策略安全,相比UglifyJS,自动优化代码的程度较保守

Closure Compiler 的Advanced模式直接破坏代码的结构,bug多

压缩有了,但是当a文件和b文件都引用了c文件的方法时,如果把c文件分别和a、b合并,这样就只有两个文件了。这就是最开始的合并方式。
一般是通过在windows上用bat脚本或者mac/linux上的shell脚本来决定合并哪些文件、用什么工具压缩、怎么压缩

进步:

解决当时网速普遍较慢的情况下网页加载资源较慢的问题

代码混淆了不容易被盗用

存在的问题:

工程化的项目里相互依赖关系变得非常复杂

合并的文件里可能会有很多无用代码

2. 自动构建的尝试阶段

通过脚本构建的项目里的文件相互依赖复杂,命名什么的完全有可能一不小心就冲突了,而且依赖可能是一层一层依赖下去的,维护起来要命呀。
所以先解决js相互依赖的问题

1. js依赖关系的规范探索
CommonJS规范解决了js模块化依赖的问题
CommonJs规范的简单介绍(nodejs版)

一个文件就是一个模块

每个模块内部,都有一个module对象,代表当前模块,有规定好的一些默认属性

module.exports属性:初始值是一个空对象{},这个变量把定义的变量、方法暴露出去

exports变量:Node为每个模块提供一个exports变量,指向module.exports。至于两者的区别,不用去了解,平常就用module.exports

require引用:把module.exports定义的变量/方法来过来用

注意:它是同步的

既然CommonJS提供了模块化的思路,也已经在服务端(nodejs)里大展手脚。那么浏览器里可不可行?
浏览器不兼容CommonJS的原因首先是,缺少了4个NodeJs环境变量:

module

exports

require

global

那么只需要提供了这些个环境变量就行了吧。Browerify就是做这的

Browserify 是目前最常用的 CommonJS 格式转换的工具

Browserify的核心思路是讲module暴露出的模块放入一个数组,require时根据模块id找到相应的module执行,总之就是给上面缺少的变量写成可执行的es5的策略

那么是不是这样就能在浏览器上愉快使用CommonJS?

CommoJS是同步require的方式获取js模块,在浏览器上会阻断主线程。页面会因加载js可能卡住

这肯定是不能容忍的

于是AMD(异步模块定义)诞生
AMD也采用require()语句加载模块,但是要传两个参数require([module], callback)。是的,回调思路

AMD规范的简单介绍(RequireJS)

解决两个问题:

(1)实现js文件的异步加载,避免网页失去响应
(2)管理模块之间的依赖性,便于代码的编写和维护

模块必须采用特定的define()函数来定义

非AMD的第三方库加载之前要用require.config()定义固有特征

CMD规范 和AMD大同小异,具体实现是seajs。没用过,应该都差不多吧,啊哈哈

2. html/css模块化的规范
less,sass,stylus 的 css 预处理器简化css语法
ejs,jade 等html的模板语法

这些真的是前端狗的福音,不多说,css-next来了,继续啃咯。

这样html/css/js 就都有了适合自动构建的扩展结构。但是这时候写一个构建这些依赖的命令太长太复杂,所以打包工具开始流行:

3.Grunt/Gulp 流处理构建工具让前端构建更容易
grunt 写法简单,插件还贼多
gulp 效率更高,可扩展性更强

nodejs配合这俩大佬做web项目的自动化构建用着都挺爽的

var gulp = require("gulp")
var nodemon = require("gulp-nodemon")
var browserSync = require("browser-sync").create()

gulp.task("nodemon", function(cb) {
    var started = false
    return nodemon({
        script: "mswadmin.js"
        , ext: "js"
        , env: { "NODE_ENV": "default" }  
    }).on("start", function() {
      if (!started) {
          cb();
        started = true;
      }
    })
});
gulp.task("serve", function(){
    browserSync.init({
    proxy: "http://10.3.10.27:18282",
    browser: "chrome",
    port: 18282
  })
    gulp.watch("static/**/*.+(scss|jade|ls)", ["inject"])
    .on("change", browserSync.reload);
})
gulp.task("default", ["nodemon","serve"]);

上面是一个用nodemon监控本地服务+watch代码热更新的配置。可以看出,以流任务的方式一个个执行。用起来也简单

2. SPA(Single-page application)来了
js 对应的 AMD 模块,然后该 AMD 模块渲染对应的 html 到容器内

这样网页不再是传统的文档一类的页面了。而是更像一个完整的程序。一个主入口,js完成的前端路由,AMD模块完成页面内重新渲染。
虽然是做出来这个SPA了,但是小问题多:

很多成熟的第三方库不支持AMD规范,引用起来贼麻烦

RequireJS在加载html依赖时,html里的img路径要使用绝对路径

只能一次性加载所有css文件

分模块打包js文件时的通用依赖项很难配置

最重要的,AMD/CMD CommonJS规范太多造成很多第三方库对规范支出不够。。。而且ES6规范都要普及了,你不用???

3. webpack来解救你

首先,webpack是静态模块打包器(bundler),grunt/gulp是流任务执行器。
区分两者可以用grunt-webpack形象说明:你可以将 webpack 或 webpack-dev-server 作为一项任务(task)执行

webpack为啥好用:

webpack 能够为ES6的 import/export 提供开箱即用般的支持

还支持CommonJS CMD/AMD模块规范,做到随时可用

这两点是我觉得最突出的地方,详细对比请参考对比

浏览器环境下,用了ES6规范的话,你应该不想用其他的了

webpack的工作步骤如下:

从入口文件开始递归地建立一个依赖关系图。

把所有文件都转化成模块函数。

根据依赖关系,按照配置文件把模块函数分组打包成若干个bundle。

通过script标签把打包的bundle注入到html中,通过manifest文件来管理bundle文件的运行和加载。

打包的规则为:一个入口文件对应一个bundle。该bundle包括入口文件模块和其依赖的模块。按需加载的模块或需多带带加载的模块则分开打包成其他的bundle。

除了这些bundle外,还有一个特别重要的bundle,就是manifest.bundle.js文件,即webpackBootstrap。这个manifest文件是最先加载的,负责解析webpack打包的其他bundle文件,使其按要求进行加载和执行。
无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 webpack_require 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

webpack 怎么入门

虽然网上有很多 十分钟入门webpack 的教程。但还是推荐去撸一遍webpack官方指南

个人觉得指南里你要注意的细节:

webpack 不会更改代码中除 import 和 export 语句以外的部分。如果你在使用其它 ES2015 特性,请确保你在 webpack 的 loader 系统中使用了一个像是 Babel 或 Bublé 的转译器

npm脚本运行时默认可以使用npx命令

source map要合理使用

留意webpack-dev-middleware,配合express做服务端渲染要用到哦

HMR(模块热替代)一般用你选用的框架自带的loader(vue-loader)

用UglifyJsPlugin插件自动移除 JavaScript 上下文中的未引用代码(dead-code)。webpack4里使用 mode=production 替代。要结合SideEffects使用,webpack4又提供了SideEffects插件使用的方式

process.env.NODE_ENV === "production" ? "[name].[hash].bundle.js" : "[name].bundle.js" 这样的条件语句在配置文件里无法使用,用if/else

splitChunks优化,webpack4已经移除了CommonsChunkPlugin。下文会详细解释

dynamic imports(动态导入)优化,chunkFilename决定非入口 chunk 的名称,vue里的运用实例就是路由懒加载(vue-lazyload),生成了新的bundle

4. webpack的优化点的补充说明

动态导入在vue里的时间注意点:

webpack 可以使用dynamic imports的方式引用模块,我们使用 async/ await 和 dynamic import 来实现。每一个dynamic import都将作为一个多带带的chunk打包。在vue中的一个例子就是路由懒加载+babel-plugin-dynamic-import-node的构建方案。使用babel-plugin-dynamic-import-node是因为开发环境下触发热更新很慢,这个插件讲import异步全部改成require同步

打包生成的文件模块标识符的问题

一般来说我们在dist生成了一下三种bundle

main bundle 会随着自身的新增内容的修改,而发生变化。
vendor bundle 会随着自身的 module.id 的修改,而发生变化。
manifest bundle 会因为当前包含一个新模块的引用,而发生变化。

然而我们并不希望vendor每次构建都生成新的hash,毕竟我们希望用到缓存的。解决方法官方有两个插件NamedModulesPlugin和HashedModuleIdsPlugin
vue里使用的是HashedModuleIdsPlugin

相信很多人从webpack3升级到4会碰到问题,接下来
### 5. 升级到webpack4你该搞明白

1. 零配置的概念把配置门槛降低了

主要使用了模式的概念。

development 模式下,默认开启了NamedChunksPlugin 和NamedModulesPlugin方便调试,提供了更完整的错误信息,更快的重新编译的速度
production 模式下,由于提供了splitChunks和minimizer,所以基本零配置,代码就会自动分割、压缩、优化,同时 webpack 也会自动帮你 Scope hoisting(作用域提升) 和 Tree-shaking

相当于把一些基本的配置当成默认配置。只需要在命令行运行时带上mode参数就搞定
#### 2. 一些插件的废除和替换

废弃了 顶替者(用optimization属性) 变化
uglifyjs-webpack-plugin minimizer 压缩优化
CommonsChunkPlugin splitChunks 代码分割,下面详解

还有一些新的插件:Tree Shaking,SideEffects。我还不知道怎么用--

3. 要注意的新的优化点

extract-text-webpack-plugin -> mini-css-extract-plugin

它与extract-text-webpack-plugin最大的区别是:它在code spliting的时候会将原先内联写在每一个 js chunk bundle的 css,多带带拆成了一个个 css 文件。js变得更干净了,css是根据optimization.splitChunks的配置自动拆分css文件为多带带的模块的规则拆分的,不用担心过多的httlp资源请求问题

所有的[chunkhash] ->[contenthash]

这是为了解决当css与js文件有依赖时,两者有相同的chunkhash。这样js修改了,css没改的情况下chunkhash页被修改了,没法缓存了呀
contenthash 你可以简单理解为是 moduleId + content 所生成的 hash
相关issue

代码的压缩优化改成了optimization.minimizer

在optimization.minimizer里推荐使用optimize-css-assets-webpack-plugin直接配置。但是vue-cli3里的配置自己配的。嗯...反正也不想看那些配置,就这样吧~~~

4. 第三方库和业务代码分开打包策略

上面多处提到了这个optimization.splitChunks

Webpack 4 最大的改进便是Code Splitting chunk。webpack3是通过CommonsChunkPlugin拆分的。然后现在直接被废弃了,我能怎么办?,跟着学呗。

开启Code Splitting很简单,使用production的mode就行,会自动开启。并有一个设置好了的一个很合理的配置

如果同时满足下列条件,chunk 就会被拆分:

新的 chunk 能被复用,或者模块是来自 node_modules 目录

新的 chunk 大于 30Kb(min+gz 压缩前)

按需加载 chunk 的并发请求数量小于等于 5 个

页面初始加载时的并发请求数量小于等于 3 个

默认配置已经很合理了,然而当出现如下情况:
已vue-cli创建的项目为例。项目用到了第三方的UI组件库,在main.js入口处依赖了第三方库。
因为在入口引入了,所以第三方库会被打包进app.js。这样,只要我修改了app.js里的其他代码,打出来的包的hash就变了。浏览器又得再次缓存app.js。第三库相当于又被缓存了一次,这显然不是我们想要的。

看一下花裤衩的配置

splitChunks: {
  chunks: "all",
  cacheGroups: {
    libs: {
      name: "chunk-libs",
      test: /[/]node_modules[/]/,
      priority: 10,
      chunks: "initial" // 只打包初始时依赖的第三方
    },
    elementUI: {
      name: "chunk-elementUI", // 多带带将 elementUI 拆包
      priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
      test: /[/]node_modules[/]element-ui[/]/
    },
    commons: {
      name: "chunk-commons",
      test: resolve("src/components"), // 可自定义拓展你的规则
      minChunks: 2, // 最小共用次数
      priority: 5,
      reuseExistingChunk: true
    }
  }
};

主要思路就是

把初始化时依赖的第三方打包成基础类库,这一类改动小,又被全局需要

把类似elementUI这一类的比较大、改动较小的抽出来

全局公用的router、函数、svg图标、layout布局组件等这些不管,直接扔app.js

业务里会经常使用但没在main.js引入的的components被打包成一个common

业务里经常使用但是体积相当较小,就直接在main.js引入,打包进app.js

其他的低频使用的组件会自动按默认splitChunks的设置来拆分

提醒: 代码的拆分一定要结合项目的实际情况,比如你就用到element里的一两个组件,完全可以按需加载在main.js,然后直接打包进app.js。所以没有最合理的拆分规则,只有最适合你的。

5. Prefetching/Preloading modules

支持了Prefetching/Preloading浏览器资源加载优化
核心思想是减少JS下载时间
学不动了学不动了,先缓缓

6. 最后推荐http://webpack.wuhaolin.cn/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/114017.html

相关文章

  • 带你了解webpack

    摘要:根据依赖关系,按照配置文件把模块函数分组打包成若干个。会随着自身的的修改,而发生变化。只需要在命令行运行时带上参数就搞定一些插件的废除和替换废弃了顶替者用属性变化压缩优化代码分割,下面详解还有一些新的插件,。 1. 前端工程化项目打包历史 前端工程化之前的时代略过 1. 半自动执行脚本来压缩合并文件 自从xmlhttprequest被挖掘出来,网页能够和服务端通讯,js能做的事越来越多...

    Tamic 评论0 收藏0
  • 库,组件,框架 - 收藏集 - 掘金

    摘要:哈哈,我理解,架构就是骨架,如下图所示译年月个有趣的和库前端掘金我们创办的使命是让你及时的了解开发中最新最酷的趋势。 翻译 | 上手 Webpack ? 这篇就够了! - 掘金译者:小 boy (沪江前端开发工程师) 本文原创,转载请注明作者及出处。 原文地址:https://www.smashingmagazine.... JavaSrip... 读 Zepto 源码之代码结构 - ...

    tommego 评论0 收藏0
  • webpack4带你实现一个vue的打包的项目

    摘要:一个用打包的的项目,参照的配置,一步一步带你实现一个的打包的项目,每一个对应一个步骤。如下注释模板生产环境的开启静态文件的压缩如果是的话需要打包完成显示包大小的状态分析运行或者就可以实现打包项目啦。 一个用webpack4打包的vue 的项目,参照vue-cli的webpack配置, 一步一步带你实现一个vue的打包的项目,每一个commit对应一个步骤。 github 地址 clon...

    verano 评论0 收藏0
  • 手摸手,带你用vue撸后台 系列一(基础篇)

    摘要:详细具体的使用可以见文章手摸手,带你优雅的使用。为了加速线上镜像构建的速度,我们利用源进行加速并且将一些常见的依赖打入了基础镜像,避免每次都需要重新下载。 完整项目地址:vue-element-admin系类文章二:手摸手,带你用vue撸后台 系列二(登录权限篇)系类文章三:手摸手,带你用vue撸后台 系列三(实战篇)系类文章四:手摸手,带你用vue撸后台 系列四(vueAdmin 一...

    xiaotianyi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<