为了账号安全,请及时绑定邮箱和手机立即绑定

求问,什么是WebPack,为什么要使用它?

/ 猿问

求问,什么是WebPack,为什么要使用它?

慕姐829404 2019-11-11 13:09:10

什么是WebPack,为什么要使用它


查看完整描述

4 回答

?
jeck猫

为什要使用WebPack
现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法
模块化,让我们可以把复杂的程序细化为小的文件;
类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别;
Scss,less等CSS预处理器
...
这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack类的工具的出现提供了需求。
什么是Webpack
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

查看完整回答
反对 回复 2019-11-16
?
SMILET

 几乎所有业务的开发构建都会用到 webpack 。的确,作为模块加载和打包神器,只需配置几个文件,加载各种 loader 就可以享受无痛流程化开发。但对于 webpack 这样一个复杂度较高的插件集合,它的整体流程及思想对我们来说还是很透明的。那么接下来我会带你了解 webpack 这样一个构建黑盒,首先来谈谈它的流程。

  准备工作

  1. webstorm 中配置 webpack-webstorm-debugger-script

  在开始了解之前,必须要能对 webpack 整个流程进行 debug ,配置过程比较简单。

  先将 webpack-webstorm-debugger-script 中的软件外包企业公司http://www.yingtaow.com?webstorm-debugger.js 置于 webpack.config.js 的同一目录下,搭建好你的脚手架后就可以直接 Debug 这个 webstorm-debugger.js 文件了。

  2. webpack.config.js 配置

  估计大家对 webpack.config.js 的配置也尝试过不少次了,这里就大致对这个配置文件进行个分析。

  var path = require('path');

  var node_modules = path.resolve(__dirname, 'node_modules');

  var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');

  module.exports = {

  // 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一个 chunk。

  entry: {

  bundle: [

  'webpack/hot/dev-server',

  'webpack-dev-server/client?http://localhost:8080',

  path.resolve(__dirname, 'app/app.js')

  ],

  },

  // 文件路径指向(可加快打包过程)。

  resolve: {

  alias: {

  'react': pathToReact

  }

  },

  // 生成文件,是模块构建的终点,包括输出文件与输出路径。

  output: {

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

  filename: '[name].js',

  },

  // 这里配置了处理各模块的 loader ,包括 css 预处理 loader ,es6 编译 loader,图片处理 loader。

  module: {

  loaders: [

  {

  test: /\.js$/,

  loader: 'babel',

  query: {

  presets: ['es2015', 'react']

  }

  }

  ],

  noParse: [pathToReact]

  },

  // webpack 各插件对象,在 webpack 的事件流中执行对应的方法。

  plugins: [

  new webpack.HotModuleReplacementPlugin();

  ]

  };

  除此之外再大致介绍下 webpack 的一些核心概念:

  loader : 能转换各类资源,并处理成对应模块的加载器。loader 间可以串行使用。

  chunk : code splitting后的产物,也就是按需加载的分块,装载了不同的module。

  对于module和chunk的关系可以参照webpack官方的这张图:

  plugin : webpack 的插件实体,这里以 UglifyJsPlugin 为例。

  function UglifyJsPlugin(options) {

  this.options = options;

  }

  module.exports = UglifyJsPlugin;

  UglifyJsPlugin.prototype.apply = function(compiler) {

  compiler.plugin("compilation", function(compilation) {

  compilation.plugin("build-module", function(module) {

  });

  compilation.plugin("optimize-chunk-assets", function(chunks, callback) {

  // Uglify 逻辑

  });

  compilation.plugin("normal-module-loader", function(context) {

  });

  });

  };

  在 webpack 中你经常可以看到 compilation.plugin('xxx', callback) ,你可以把它当作是一个事件的绑定,这些事件在打包时由 webpack 来触发。

  3. 流程总览

  在具体流程学习前,可以先通过这幅 webpack整体流程图 了解一下大致流程(建议保存下来查看)。

  shell 与 config 解析

  每次在命令行输入 webpack 后,操作系统都会去调用 ./node_modules/.bin/webpack 这个 shell 脚本。这个脚本会去调用./node_modules/webpack/bin/webpack.js 并追加输入的参数,如 -p , -w 。(图中 webpack.js 是 webpack 的启动文件,而 $@ 是后缀参数)

  在 webpack.js 这个文件中 webpack 通过 optimist 将用户配置的 webpack.config.js 和 shell 脚本传过来的参数整合成 options 对象传到了下一个流程的控制对象中。

  1. optimist

  和 commander 一样,optimist 实现了 node 命令行的解析,其 API 调用非常方便。

  var optimist = require("optimist");

  optimist

  .boolean("json").alias("json", "j").describe("json")

  .boolean("colors").alias("colors", "c").describe("colors")

  .boolean("watch").alias("watch", "w").describe("watch")

  ...

  获取到后缀参数后,optimist 分析参数并以键值对的形式把参数对象保存在 optimist.argv 中,来看看 argv 究竟有什么?

  // webpack --hot -w

  {

  hot: true,

  profile: false,

  watch: true,

  ...

  }

  2. config 合并与插件加载

  在加载插件之前,webpack 将 webpack.config.js 中的各个配置项拷贝到 options 对象中,并加载用户配置在 webpack.config.js 的 plugins 。接着 optimist.argv 会被传入到 ./node_modules/webpack/bin/convert-argv.js 中,通过判断 argv 中参数的值决定是否去加载对应插件。(至于 webpack 插件运行机制,在之后的运行机制篇会提到)

  ifBooleanArg("hot", function() {

  ensureArray(options, "plugins");

  var HotModuleReplacementPlugin = require("../lib/HotModuleReplacementPlugin");

  options.plugins.push(new HotModuleReplacementPlugin());

  });

  ...

  return options;

  options 作为最后返回结果,包含了之后构建阶段所需的重要信息。

  {

  entry: {},//入口配置

  output: {}, //输出配置

  plugins: [], //插件集合(配置文件 + shell指令)

  module: { loaders: [ [Object] ] }, //模块配置

  context: //工程路径

  ...

  }

  这和 webpack.config.js 的配置非常相似,只是多了一些经 shell 传入的插件对象。插件对象一初始化完毕, options 也就传入到了下个流程中。

  var webpack = require("../lib/webpack.js");

  var compiler = webpack(options);

  编译与构建流程

  在加载配置文件和 shell 后缀参数申明的插件,并传入构建信息 options 对象后,开始整个 webpack 打包最漫长的一步。而这个时候,真正的 webpack 对象才刚被初始化,具体的初始化逻辑在 lib/webpack.js 中,如下:

  function webpack(options) {

  var compiler = new Compiler();

  ...// 检查options,若watch字段为true,则开启watch线程

  return compiler;

  }

  ...

  webpack 的实际入口是 Compiler 中的 run 方法,run 一旦执行后,就开始了编译和构建流程 ,其中有几个比较关键的 webpack 事件节点。

  compile 开始编译

  make 从入口点分析模块及其依赖的模块,创建这些模块对象

  build-module 构建模块

  after-compile 完成构建

  seal 封装构建结果

  emit 把各个chunk输出到结果文件

  after-emit 完成输出

  1. 核心对象 Compilation

  compiler.run 后首先会触发 compile ,这一步会构建出 Compilation 对象:

  compilation类图

  这个对象有两个作用,一是负责组织整个打包过程,包含了每个构建环节及输出环节所对应的方法,可以从图中看到比较关键的步骤,如 addEntry() , _addModuleChain() , buildModule() , seal() , createChunkAssets() (在每一个节点都会触发 webpack 事件去调用各插件)。二是该对象内部存放着所有 module ,chunk,生成的 asset 以及用来生成最后打包文件的 template 的信息。

  2. 编译与构建主流程

  在创建 module 之前,Compiler 会触发 make,并调用 Compilation.addEntry 方法,通过 options 对象的 entry 字段找到我们的入口js文件。之后,在 addEntry 中调用私有方法 _addModuleChain ,这个方法主要做了两件事情。一是根据模块的类型获取对应的模块工厂并创建模块,二是构建模块。

  而构建模块作为最耗时的一步,又可细化为三步:

  调用各 loader 处理模块之间的依赖

  webpack 提供的一个很大的便利就是能将所有资源都整合成模块,不仅仅是 js 文件。所以需要一些 loader ,比如 url-loader ,jsx-loader , css-loader 等等来让我们可以直接在源文件中引用各类资源。webpack 调用 doBuild() ,对每一个 require() 用对应的 loader 进行加工,最后生成一个 js module。

  Compilation.prototype._addModuleChain = function process(context, dependency, onModule, callback) {

  var start = this.profile && +new Date();

  ...

  // 根据模块的类型获取对应的模块工厂并创建模块

  var moduleFactory = this.dependencyFactories.get(dependency.constructor);

  ...

  moduleFactory.create(context, dependency, function(err, module) {

  var result = this.addModule(module);

  ...

  this.buildModule(module, function(err) {

  ...

  // 构建模块,添加依赖模块

  }.bind(this));

  }.bind(this));

  };

  调用 acorn 解析经 loader 处理后的源文件生成抽象语法树 AST

  Parser.prototype.parse = function parse(source, initialState) {

  var ast;

  if(!ast) {

  // acorn以es6的语法进行解析

  ast = acorn.parse(source, {

  ranges: true,

  locations: true,

  ecmaVersion: 6,

  sourceType: "module"

  });

  }

  ...

  };

  遍历 AST,构建该模块所依赖的模块

  对于当前模块,或许存在着多个依赖模块。当前模块会开辟一个依赖模块的数组,在遍历 AST 时,将 require() 中的模块通过addDependency() 添加到数组中。当前模块构建完成后,webpack 调用 processModuleDependencies 开始递归处理依赖的 module,接着就会重复之前的构建步骤。

  Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {

  // 根据依赖数组(dependencies)创建依赖模块对象

  var factories = [];

  for(var i = 0; i < dependencies.length; i++) {

  var factory = _this.dependencyFactories.get(dependencies[i][0].constructor);

  factories[i] = [factory, dependencies[i]];

  }

  ...

  // 与当前模块构建步骤相同

  }

  3. 构建细节

  module 是 webpack 构建的核心实体,也是所有 module的 父类,它有几种不同子类:NormalModule , MultiModule ,ContextModule , DelegatedModule 等。但这些核心实体都是在构建中都会去调用对应方法,也就是 build() 。来看看其中具体做了什么:

  // 初始化module信息,如context,id,chunks,dependencies等。

  NormalModule.prototype.build = function build(options, compilation, resolver, fs, callback) {

  this.buildTimestamp = new Date().getTime(); // 构建计时

  this.built = true;

  return this.doBuild(options, compilation, resolver, fs, function(err) {

  // 指定模块引用,不经acorn解析

  if(options.module && options.module.noParse) {

  if(Array.isArray(options.module.noParse)) {

  if(options.module.noParse.some(function(regExp) {

  return typeof regExp === "string" ?

  this.request.indexOf(regExp) === 0 :

  regExp.test(this.request);

  }, this)) return callback();

  } else if(typeof options.module.noParse === "string" ?

  this.request.indexOf(options.module.noParse) === 0 :

  options.module.noParse.test(this.request)) {

  return callback();

  }

  }

  // 由acorn解析生成ast

  try {

  this.parser.parse(this._source.source(), {

  current: this,

  module: this,

  compilation: compilation,

  options: options

  });

  } catch(e) {

  var source = this._source.source();

  this._source = null;

  return callback(new ModuleParseError(this, source, e));

  }

  return callback();

  }.bind(this));

  };

  对于每一个 module ,它都会有这样一个构建方法。当然,它还包括了从构建到输出的一系列的有关 module 生命周期的函数



查看完整回答
反对 回复 2019-11-16
?
Cats萌萌

可以考虑开发的时候,页面中加载的是源码,然后用livereload实现有修改,自动刷新页面
然后在进行release发布的时候,打包编译,测试看看有没有问题。
这样进行编译的次数就少了。webpack是可以支持requirejs的AMD的写法的吧

查看完整回答
反对 回复 2019-11-16
?
狐的传说

只是一个别名而已。这里设置别名是为了让后续引用的地方减少路径的复杂度。

//例如
src
- components
- a.vue
- router
- home
- index.vue

index.vue 里,正常引用 A 组件:

import A from '../../components/a.vue'

如果设置了 alias 后。
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}

引用的地方路径就可以这样了

import A from '@/components/a.vue'

这里的 @ 就起到了【resolve('src')】路径的作用。


查看完整回答
反对 回复 2019-11-16
  • 4 回答
  • 0 关注
  • 72 浏览
我要回答

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信