详解 webpack 之令人困惑的地方

原文 Webpack——The Confusing Parts

Webpack是目前基于React和Redux开发的应用的主要打包工具。我想使用Angular 2或其他框架开发的应用也有很多在使用Webpack。

当我第一次看到Webpack的配置文件时,它看起来非常的陌生,我非常的疑惑。经过一段时间的尝试之后我认为这是因为Webpack只是使用了比较特别的语法和引入了新的原理,因此会让使用者感到疑惑。这些也是导致Webpack不被人熟悉的原因。

因为刚开始使用Webpack很让人疑惑,我觉得有必要写几篇介绍Webpack的功能和特性的文章以帮助初学者快速理解。此文是最开始的一篇。


Webpack的核心原理

webpack的两个最核心的原理分别是:

1.一切皆模块

   正如 js 文件可以是一个“模块(module)一样”,其他的(如css,images,html)文件也可以作为模块。因此,你可以require('myJSfile.js),也可以require('myCSSfile.css')。这意味着我们可以将事物(业务)分割成更小的易于管理的模块,从而达到重复利用等目的。

2.按需加载

   传统的模块打包工具(modules bundlers)最终将所有的模块编译生成一个庞大的 bundle.js 文件。但是在真实的app里面,“bundle.js” 文件可能有10M到15M之大,可能会导致应用一直处于加载中状态。因此,webpack 使用许多特性来分割代码然后生成多个 bundle 文件,而且异步加载部分代码以实现按需加载。

好了,接下来看一下那些各种各样令人困惑的地方吧。

1.开发模式和生产模式

    首先要知道的是,Webpack有很多的特性。一些是“开发模式”下有的,一些是“生产模式”下有的,还有一些是两种模式下都有的。

通常,使用这么多特性的项目,都会有两个大型webpack配置文件。

为了生成bundles文件你可能在package.json文件加入以下的scripts项:

"scripts": {
  // 运行npm run build 来编译生成生产模式下的bundles
  "build": "webpack --config webpack.config.prod.js",
  // 运行npm run dev来生成开发模式下的bundles以及启动本地server
  "dev": "webpack-dev-server"
 }

 2.webpack CLI 和 webpack-dev-server

    值得注意的是,Webpack作为模块打包工具,提供两种用户交互接口:

(1)webpack cli tool : 默认的交互方式(已随webpack本身安装到本地)

(2)webpack-dev-server: 一个Node.js 服务器(需要开发者从npm自行安装)

Webpack CLI(有利于生产模式下打包)

这种方式可以从命令行获取参数,也可以从配置文件(默认的webpack.config.js)中获取,将获取到的参数传入webpack来打包

当然,你也可以从命令行(CLI)开始学习webpack,以后你可能主要在生产模式下使用到它。

用法

方式1: 

// 全局模式安装webpack
   npm install webpack --g

// 在终端输入
   $ webpack // <--使用 webpack.config.j s生成 bundle

方式 2 :

// 非全局模式(局部)安装 webpack 然后添加到 package.json 依赖里边
   npm install webpack --save

// 添加build命令到package.json的scripts配置项
   "scripts": {
    "build": "webpack --config webpack.config.prod.js -p",
    ...
   }

// 通过运行以下方法来使用它:
   "npm run build"

webpack-dev-server(有利于在开发模式下编译)

这是一个基于 Express.js 框架开发的web server,默认监听8080端口。server 内部调用 webpack,这样做的好处是提供了额外的功能,如热更新‘Live Reload’ 以及热替换 ‘Hot Module Replacement’ (即HMR)。

用法

方式 1:

// 全局安装
   npm install webpack-dev-server --save

// 终端输入
   $ webpack-dev-server --inline --hot

方式 2:

// 添加到package.json scripts
   "scripts": {
   "start": "webpack-dev-server --inline --hot",
   ...
   }

// 运行: 
  $ npm start

// 浏览器预览:
  http://localhost:8080

Webpack VS Webpack-dev-server选项

值得注意的是,inlinehot这些选项是Webpack-dev-server特有的,而其他的一些,如hide-modules则是CLI模式特有的选项。

webpack-dev-server CLI选项和配置项

另外要注意的是,您可以通过两种方式将参数传递给 webpack-dev-server

       1.通过 webpack.config.js 文件的 'devServer' 对象

       2.通过cli 选项

// 通过CLI传参
   webpack-dev-server --hot --inline

// 通过webpack.config.js传参
   devServer: {
   inline: true,
   hot:true
  }

我发现有时devServer配置项(hot: true 和inline: true)不生效,所以我更喜欢如下的方式向CLI传递参数:

// package.json
  {
    "scripts": "webpack-dev-server --hot --inline"
  }

:确定你没有同时传入hot:true-hot

webpack-dev-server的“hot” 和 “inline”选项

“inline”选项会为入口页面添加“热加载”功能,“hot”选项则开启“热替换(Hot Module Reloading)”,即尝试重新加载组件改变的部分(而不是重新加载整个页面)。如果两个参数都传入,当资源改变时,webpack-dev-server将会先尝试HRM(即热替换),如果失败则重新加载整个入口页面。

// 当资源发生改变,以下三种方式都会生成新的bundle,但是又有区别:

// 1. 不会刷新浏览器
      $ webpack-dev-server

//2. 刷新浏览器
      $ webpack-dev-server --inline

//3. 重新加载改变的部分,HRM失败则刷新页面
      $ webpack-dev-server  --inline --hot

 3."entry": 值分别是字符串、数组和对象的情况

Enter配置项告诉 webpack 应用的根模块或起始点在哪里,它的值可以是字符串、数组或对象。这看起来可能令人困惑,因为不同类型的值有着不同的目的。

像绝大多数app一样,倘若你的应用只有一个单一的入口,enter项的值你可以使用任意类型,最终输出的结果都是一样的。

不同的类型但输出相同,如图所示:

entry:数组类型

但是,如果你想要添加多个彼此不相互依赖的文件。你可以使用Array格式的值

例如:你的HTML中可能需要“googleAnalytics.js”。你可以告诉webpack将它添加到bundle.js的末尾,如下所示:

enter:对象

现在,如果假设你有多页面应用程序,但具有多个HTML文件(index.html和profile.html),而不是具有多视图的SPA。你就可以通过一个对象告诉webpack,为每个html生成一个bundle文件。

下面的配置将生成两个JS文件:indexEntry.js和profileEntry.js,你可以分别在index.html 和 profile.html 中使用它们

用法

//profile.html
 <script src=”dist/profileEntry.js”></script>

//index.html
 <script src=”dist/indexEntry.js”></script>

:文件名取自“entry”对象的键名。 

enter:混合类型

你也可以在enter对象里使用数组类型,例如下面的配置将会生成3个文件:vender.js(包含三个文件),index.js和profile.js文件。


 4. output:“path”项和“publicPath”项

output 告诉 webpack 怎样存储输出结果以及存储到哪里。output 的两个属性 “path”和“publicPath” 可能会造成困惑。

“path ”只告诉 webpack 结果存储在哪里,然而“publicPath” 则被许多 webpack 的插件用于在生产模式下,更新内嵌到css、html文件里的url值。

例如,在localhost 里的css文件中,你可能用“./test.png”这样的url来加载图片,但是在生产模式下“test.png”文件可能会定位到CDN上,并且你的Node.js服务器可能是运行在HeroKu上边的。这就意味着在生产环境中,你必须手动更新所有文件里的url为CDN的路径。

相反,你也可以使用webpack的publicPath, 和一系列支持publicPath的插件在生产模式下编译输出文件时自动更新URL。

// 开发环境:Server和图片都是在localhost(域名)下
  .image { 
     background-image: url('./test.png');
   }

// 生产环境:Server部署下HeroKu但是图片在CDN上
  .image { 
     background-image: url('https://someCDN/test.png');
   }

5.模块加载和链式模块加载

模块加载器是可自由添加的Node模块,用于将不同类型的文件“load” 或“import”转换为浏览器可以识别的类型,如js,Stylesheet等。更高级的模块加载器甚至可以支持使用ES6里边的“require”或“import”引入模块。

例如,你可以使用 bable-loader 来将使用ES6语法编写的js转换为浏览器可接受的ES5,如下所示:

module: {
 loaders: [{
  test: /\.js$/, // 匹配.js文件,如果通过则使用下面的loader
  exclude: /node_modules/, // 排除node_modules文件夹
  loader: 'babel' // 使用babel(babel-loader的简写)作为loader
 }]

链式(管道式)的加载器 (从右往左执行) 

多个loader可以用在同一个文件上并且被链式调用。链式调用时从右到左,并且之间用"!"分隔。

例如,假设我们有一个css文件“myCssFile.css”,我们想将它的内容输出到html的style标签中,我们可以使用两个加载器来完成它:css-loader 和 style-loader

module: {
 loaders: [{
  test: /\.css$/,
  loader: 'style!css' //(short for style-loader!css-loader)
 }]

以下展示它的工作原理:

(1)webpack 在模块内搜索css的依赖项,即webpack检查js文件是否有“require(‘myCssFile’)”的引用,如果它发现有css的依赖,webpack将css文件交给“css-loader”去处理

(2)css-loader 加载所有的css文件以及自身的依赖到JSON对象里,webpack将处理将结果传给 “style-loader”

(3)style-loader 接受JSON并将其添加到style标签后插入到index.html中

6.loader 自身可配置

loader(模块加载器)自身可以根据传入不同的参数进行配置

在下面的例子中,我们可以配置 url-loader 来将小于1024字节的图片使用DataUrl 替换,而大于1024字节的图片使用url,我们可以用如下两种方式,通过传入 “limit” 参数来实现这一目的

7. .babelrc 文件

babal-loader使用”presets“配置项来标识如何将ES6语法转成ES5以及如何转换React的JSX成js文件。我们可以用如下的方式使用”query“参数传入配置:

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel',
      query: {
        presets: ['react', 'es2015']
      }
    }
  ]
}

然而,在许多项目中,bable的配置可能会变的非常大。因此,你可以将它们保存在一个名为 .babelrc 文件的 bable-loader 的配置文件中,在执行时 bable-loader 将会自动加载 .babelrc 文件

所以在很多例子中,你可能会看到:

//webpack.config.js 
module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel'
    }
  ]
}

//.bablerc
{
 presets: ['react', 'es2015']
}

8.插件

插件一般都是用于输出 bundle.js 的node模块。

例如:uglifyJSPlugin获取bundle.js然后压缩和混淆内容以减小文件体积

类似的extract-text-webpack-plugin内部使用 css-loader 和 style-loader 来收集所有的css到一个地方,最终将结果提取到一个独立的”styles.css“文件,并且在html里边引用style.css文件。

//webpack.config.js
// 获取所有的.css文件,合并它们的内容然后提取css内容到一个独立的”styles.css“里
var ETP = require("extract-text-webpack-plugin");

module: {
 loaders: [
  {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
  ]
},
plugins: [
    new ExtractTextPlugin("styles.css") //Extract to styles.css file
  ]
}

:如果你只是想把 css 使用style 标签内联到html里,你不必使用extract-text-webpack-plugin,只使用 css-loader 和style-loader 就可以:

module: {
 loaders: [{
  test: /\.css$/,
  loader: 'style!css' // (short for style-loader!css-loader)
 }]

9.loader(加载器)和插件

你可能已经意识到了,Loader 处理单独的文件级别并且通常作用于包生成之前或生成的过程。

而插件则是处理包 或者 chunk 级别,且通常是bundle 生成的最后阶段。一些插件如 commonschunkplugin 甚至更直接修改bundle的生成方式。

10.处理文件的扩展名

很多 webpack 的配置文件都有一个 resolve 属性,然后就像下面代码所示有一个空字符串的值。空字符串在此是为了 reslove 一些在import 文件时不带文件扩展名的表达式,如 require(‘./myJSFile’)或者 import myJSFile from './myJSFile'

{
 resolve: {
   extensions: ['', '.js', '.jsx']
 }
}

就这些了,如果还有朋友想了解更多的话,可以百度一下或者关注博主,博主会不定期总结webpack 系列所遇到的问题以及解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值