markdown 的WebPack

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown 的WebPack相关的知识,希望对你有一定的参考价值。

# Webpack

## 概述
Webpack是现在非常流行的前端模块打包工具,它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
主要有1.x、2.x、3.x三个版本,2.x相对1.x的API有很大改动,3.x基本沿用2.x的API
## 安装
我们可以在全局安装webpack:
```shell
npm install webpack -g
```
不过通常情况下,我们推荐在工程目录下进行安装,这样可以有效做到项目间的隔离:
```shell
npm install webpack --save-dev
```
## 使用
webpack是一个命令行工具,因此我们可以在命令行中进行webpack打包操作,在项目目录下执行webpack命令,默认会去寻找当前项目下的webpack.config.js配置文件,另外我们也可以通过--config参数来指定webpack配置文件, 通常我们还会加上--progress参数来在执行webpack过程中查看执行进度:
```shell
webpack --progress --config ./webpack.config.dev.js
```
> 最近实践:通常情况下,我们会为项目设置两个配置文件,分别为webpack.config.js和webpack.config.dev.js,第一个应用于生产环境,我们会加入一些类似ungilfy的代码优化操作,第二个应用于开发环境,我们会设置一些sourcemap 或 devServer等帮助开发的配置。

webpack的配置文件是一个node模块,输出一个json配置,下面将简要介绍配置文件的结构及使用:
### 入口(entry)
入口主要指明webpack在打包过程中的入口文件,也是应用程序的起点,通过入口文件来梳理整个依赖关系进行打包,entry有多种配置方式:
* 单入口情况
往往针对只有一个入口文件的情况,我们可以直接将入口文件地址配在entry属性上:
```json
{
  entry: './src/index.js'
}
```
* 多入口情况
一般情况下,一个项目往往包含多个入口文件,因此我们需要用对象结构来配置多入口:
```js
{
  entry: {
    page1: './src/page1.js',
    page2: './src/page2.js'
  }
}
```
* 多入口文件情况
有些情况下,我们需要把多个入口文件打到同一个包中,我们可以通过文件路径数组的情况下来配置entry, 例如:

```js
{
  entry: {
    page1: ['./src/page1.js', './src/page12.js'],
    page2: './src/page2.js'
  }
}
```

entry还支持动态入口:
```js
{
  entry: () => './demo.js'
}
```
```js
{
  entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))
}
```
> 最佳实践:通常情况下我们的项目都是包含多入口的,因此,我们会专门在项目根目录下建立一个entries文件夹存放项目入口文件,然后通过js的方式来设置webpack的entry配置,例如:
```js

```

### 输出(output)
输出用于告诉webpack打包完的文件的存放位置。output至少需要包含两个属性:
* filename: 输出文件的文件名;
* path: 输出文件目录的绝对路径;
针对不同的entry配置output也会有所不同:
* 单入口情况
对于单入口的情况,我们可以直接指定输出文件的名称:

```js
{
  output: {
    filename: 'bundle.js',
    path: __dirname + 'build'
  }
}
```
* 多入口情况
针对多入口情况,webpack会通过一个name属性将entry中配置的入口名称传递给到output(对于单入口情况,默认name为main):

```js
{
  entry: {
    page1: './src/page1.js',
    page2: './src/page2.js'
  }
  output: {
    filename: '[name].js',
    path: __dirname + 'build'
  }
}
//两个entry分别传入的name为page1和page2
```
每次代码更改重新webpack,webpack会生成一个新的hash值,我们可以在output中使用这个hash值:
```js
{
  output: {
    filename: '[name].js',
    path: __dirname + 'build/[hash]'
  }
}
```

#### publicPath
此外output还可以设置最终输出文件的publishPath,这样在进行本地devServer开发过程中,我们就可以直接在script标准中设置对应的publishPath,保证本地html与线上html文件的一致性:
```js
{
  output: {
    publicPath: 'http://g.alicdn.com/example/'
  }
}
```

### 加载器(loaders)
webpack只能处理js模块,如果要处理其他类型的模块,就需要用到loader了,loader可以理解为一个模块和资源的转换器,接受源文件作为参数,返回转换结果。loader可以通过管道方式链式调用,每一个loader可以将资源转换成任意格式然后传递给下一个loader,最后一个loader执行完成以后返回webpack预期的js。

在使用loader之前,我们需要安装loader,webpack的loader的名称通常为[name]-loader的形式。

webpack提供3种方式来使用loader:
#### 配置(官方推荐)
webpack中loader的配置是以数组的形式存在在```module.rules```字段下面,每一个loader配置主要包含以下字段:
  * test: 一个正则表达式用于筛选需要loader处理的文件;
  * exclude: 用于排除不需要进行loader处理的文件;
  * include: 用于指明需要进行loader处理的文件;
  * use: 一个loader数组,用于配置loader的参数及执行顺序,loader的执行顺序是从后往前的,后一个loader的执行结果会依次传向前一个loader,最后到第一个loader执行完成以后输出;
loader的通常配置如下所示:
```js
{
  module: {
    rules: [{
      test: /\.css$/',
      exclude: /node_modules/
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            module: true
          }
        }
      ]
    }]
  }
}
//对于不需要设置参数的loader可以直接用loader名称,否则需要使用obj形式设置loader的参数。
```
#### 内联方式
webpack提供在require或者import的时候指定loader,并使用!来进行loader分隔:
```js
import Style from 'style-loader!css-loader?module!./style.css';

```
对于loader的参数可以像```?key=value&foo=bar``` 这种url的参数形式也可以是一个json对象, ```?{"key": "value", "foo": "bar"}```

#### CLI
我们也可以通过命令行工具来使用loader:
```shell
webpack --module-bind 'css=style-loader!css-loader'
```

总之,我们最好按照官方推荐,使用配置的方式来使用loader,我们会在后面的章节具体讲解常用loader的使用。
### 插件(plugins)
插件可以完成更多loader不能完成的功能,我们在plugins字段下配置webpack的插件, 我们需要在plugins中传入插件实例:
```js
{
  plugins: [{
    new webpack.optmize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  }]
}
```
另外,我们还可以通过node API来使用plugin,不过官方并不推荐这种方式:
```js
const webpack = require('webpack');
const cfg = require('./webpack.confg.js');

let compiler = webpack(cfg);
compiler.apply(new webpack.ProgressPlugin());
compiler.run((err, stats) => {
});
```

我们会在后面的章节具体讲解常用的plugin用法。
## Loaders
Webpack自身只处理js资源,对于其他类型的资源我们需要使用loader将其转换成能够被webpack识别的js资源,loader可以理解为模块到资源的转换器,接受一个源文件,返回转换结果。loader可以通过管道方式链式调用,每一个loader将资源转换成任意格式传给下一个loader,最后一个loader输出供webpack使用js资源。

我们按照转换文件类型列出常用loader列表:

### js
#### babel-loader
我们现在大部分代码都是基于ES6,甚至ES7/ES8的规范来书写,而且我们大量使用JSX语法,为了让webpack能够对这些js资源进行打包,我们需要利用babel-loader来编译js资源。

我们可以将babel的配置写在webpack配置文件中,如下所示:
```js
{
  test: /\.jsx?$/,
  use: [{
    loader: 'babel-loader',
    options: {
      presets: ['stage-0', 'react'],
      plugins: ['transform-runtime']
    }
  }]
}
```
另外babel-loader提供一些其他的参数:
* cacheDirectory:默认false,当设置时,指定的目录将用来缓存loader的执行结果,这样webpback构建会先前尝试读取缓存,来避免webpack构建过程中每次都需要执行babel编译过程,如果设置true则会默认缓存在node_modules/.cache/babel-loader目录下;
* cacheIdentifier:默认是由babel-core版本号,babel-loader版本号, .babelrc文件内容,环境变量BABEL_ENV 的值(没有时降级到 NODE_ENV)组成的字符串。可以设置一个自定义值,identifier的值改变将强制缓存失效;
* forceEnv:默认是BABEL_ENV然后是NODE_ENV。允许你在 loader 级别上覆盖 BABEL_ENV/NODE_ENV。对有不同 babel 配置的,客户端和服务端同构应用非常有用;

最佳实践:
* 将babel配置抽出到.babelrc文件中管理;
* 将node_modules及bower_components下的文件排除,不做babel编译:

```js
{
  test: /\.jsx?$/,
  exclude: /(node_modules|bower_components)/
}
```
* 将cacheDirectory选项开启,babel-loader提速至少两倍;
* 利用transform-runtime插件将babel生成的代码抽离,降低文件大小;

#### eslint-loader
eslint主要用来检测代码质量,避免不必要的低级错误,如果使用webpack我们可以使用eslint-loader在webpack执行时执行代码检测操作,同时我们也可以在webpack配置文件中配置eslint规则。

```js
{
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: [
    "babel-loader",
    "eslint-loader",
  ],
}
```

> 需要注意的是如果用到babel-loader,eslint-loader应该在babel-loader之前执行。

eslint-loader包含如下参数:
* fix: 默认为false,开启会在执行eslint过程中自动修复不符合规范的代码。建议不要开启,可能会引起webpack死循环问题;
* cache: 默认false,开启以后则会将eslint结果缓存到制定位置,这样可以在webpack打包过程中减少lint时间,如果赋值为true,默认会缓存到./node_modules/.cache;
* formatter: 默认为eslint stylish formatter,我们可以通过这个参数重新定义eslint输出;
* eslintPath: 默认为eslint,用于指定eslint实例的路径;

最佳实践
* 将eslint配置单独抽离到.eslintrc文件进行配置;
* 在开发环境下使用eslint-loader;

### 样式资源
#### style-loader
style-loader的主要作用是将css通过style标签的形式插入到dom中,通常它会和css-loader配合使用来打包css样式。

style-loader/url

可以添加一个url <link href="/path/to/file.css" rel="stylesheet">,而不是使用<style>标签内嵌css。

style-loader的主要配置有:

* hmr: 默认值为true,是否开启热替换,如果设置为false,就不会插入HMR代码;
* base:当使用一个或读个DllPlugin时,这个设置主要用作css冲突的修补方案,base可以防止app的css(或DllPlugin2的css)覆盖DllPlugin1的css;
* attrs:指定<style>/<link>的属性,如果指定,会将属性附加到对应标签上;
* transform: 是一个函数,可以在通过style-loader加载css到页面之前修改css,如果transform返回false,则css不会加载到页面中;
* insterAt:默认情况下,style-loader将<style>标签添加到样式目标(即<head>标签)的末尾,可以通过insterAt指定其他标签:

```js
// 在样式目标的起始位置插入style标签
{
  loader: 'style-loader',
  options: {
    instertAt: 'top'
  }
}
// 在指定标签前面插入style标签
{
  loader: 'style-loader',
  options: {
    insertAt: {
      before: '#id'
    }
  }
}
```

* singleton:默认为undefined,若设置为ture则所有样式模块公用同一个<style>标签,否则每一个css模块单独使用一个<style>标签。
* sourceMap: 默认false,是否开启SourceMap;
* convertToAbsoluteUrls:默认为false,将相对路径转换成绝对路径,和sourceMap配合使用,解决source map启用时相对路径无法加载的问题。

最佳实践
* 在生产环境下可以将hmr关掉,可以减小一定的文件体积;

#### css-loader
css-loader主要作用是遍历所有的css文件,解析其中的@import和url()表达式转换成import/require,然后使用合适的资源加载器(file-loader或url-loader)来进行加载。

css-loader的主要配置有:
* root:默认'/',解析URL的路径,对于以```\```开头的URL,默认不转译;
* url:默认为false,是否禁用css-loader解析url();
* import: 默认为false,是否禁用css-loader解析@import;
* alias:创建别名更容易导入一些模块;
* module:默认为false,开启css模块模式;
#### postcss-loader
postcss-loader的主要作用是使用[PostCSS](http://postcss.org/)来处理css,PostCSS是一个功能非常强大的CSS处理工具,通常情况下,我们会将postcss-loader放在style-loader和css-loader后面,其他css预处理loader(less-loader或sass-loader)前面。

postCSS通过插件方式提供了非常多的CSS扩展功能,我们用到最多的就是autoPrefix。
最佳实践
* 

#### less-loader
less-loader的主要作用是导入LESS文件并编译成css。

最佳实践
* 无
#### sass-loader
sass-loader的主要作用是导入SASS/SCSS文件并编译成css。

最佳实践
* 无
### 其他资源
#### file-loader
将引用的对象转换成文件,并返回文件的路径,默认情况下生成文件的文件名为整个文件内容的MD5hash值,扩展名为原始文件的扩展名。
#### url-loader
url-loader是file-loader的封装,功能也和file-loader类似,区别是对于体积小于限制大小时,会将文件内容直接转换成内敛dataURL的形式,减少请求。
## 插件

### UglifyJsPlugin
UglifyJsPlugin的主要作用是用来压缩js代码。

最佳实践:
* 只在生产环境下使用该插件;
* 开启parallel选项,并行进行js压缩;

### CommonsChunkPlugin
CommonsChunkPlugin的主要作用是在多入口的情况下能够提取独立的功能模块。这样公共模块就可以缓存在浏览器中,而不用每次都重复加载。

最佳实践:
* 

### ExtractTextPlugin
ExtractTextPlugin的主要作用是将文本抽出并生成单独的文件。一般用于将样式文件抽离出单独的css文件,而不是内嵌到JS bundle中,这样可以使得css和js并行加载。

最佳实践
*

### NoEmitOnErrorsPlugin
在编译出错时,跳过输出阶段,这样可以保证输出资源不包含错误,在命令行运行webpack时,使用此插件,进程遇到错误将不会退出。

### DefinePlugin
DefinePlugin可以在编译时配置项目的全局变量,这个插件对于在开发环境和生产环境构建不同的行为非常有用。
因为这个插件直接执行文本替换,因此给定的字符串必须包含字符串本身的实际引导,通常有两种方法达到这个效果: ```'"production"'```或者```JSON.stringify('production')```。

最佳实践
* 通常我们会在生产环境和开发环境下配置不同的全局变量用于在代码中区分;

```js
// 生产环境
new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('production')
});
// 开发环境
new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('development')
});
```

## devServer

## 参考

以上是关于markdown 的WebPack的主要内容,如果未能解决你的问题,请参考以下文章

markdown 的WebPack

markdown 的WebPack

markdown 的WebPack-HMR

markdown 的WebPack

markdown 的WebPack&汇总与包裹对比

markdown Webpack,es6,postcss