【译】webpack之谜

【注】本文原发自此处,转载请注明出处。css

本文译自【Webpack-The Confusing Parts】原文html

本文已同步发表在个人博客node

前言

webpack是当前最受欢迎的模块管理器(module bundler),对于使用React开发的项目来讲堪称神器。固然,对于使用其余框架,好比Angular或者Backbone等的开发者来讲,webpack也是种很好的工具。react

第一次配置webpack.config.js时,有不少地方使我很困惑。在使用了webpack一段时间后,我认识到正是这些地方让webpack如此强大和迷人。jquery

webpack核心理念

  • 一切都是模块。—— 在webpack中,不只js文件能够做为一个模块,其余文件(cssimageshtml)均可以做为模块。这就是说,你能够在其余文件中加载这些模块,require('myJSfile.js'),或者require('myCSSfile.css')。这意味着咱们将任意文件拆分红便于管理的小文件,而后经过在其余文件中加载这些小文件来达到重复利用的目的。
  • 按需加载(Load only "what" you need and "when" you need)—— 通常状况下打包工具会将咱们全部的模块打包生成一个最终的文件bundle.js。可是在实际应用中,bundle.js一般会很大(10M~15M),须要很长时间才能加载完成。webpack提供了多种code splitting的方法,会生成过个打包后的文件,且支持按需加载。这样咱们只有在须要用到某个模块的时候才会异步加载该模块。

如今咱们来看下这些使人困惑的部分。webpack

开发环境和生产环境(Development Vs Production)

首先要明确的一点是,webpack有不少特性,有些只在开发环境使用,还有些只在生产环境使用,固然还有在生产环境和开发环境均可以使用的。以下图所示:
图片描述es6

因此一般咱们会有两个 config文件,以针对开发环境和生产环境做不一样配置。

package.json作以下配置:web

"scripts": {
  //npm run build to build production bundles
  "build": "webpack --config webpack.config.prod.js",
  //npm run dev to generate development bundles and run dev. server
  "dev": "webpack-dev-server"
 }

webpack CLI Vs webpack-dev-server

须要知道webpack提供了两个接口npm

  1. webpack命令行工具(webpack CLI tool) —— 默认使用这种方式,无需单独安装,被集成在webpack中。
  2. webpack-dev-server —— node.js 服务器,须要单独安装

Webpack CLI (适用于生产环境构建)

能够经过命令行添加参数,也能够经过配置文件(默认为webpack.config.js),webpack打包时会读取这些配置。json

最初学习 webpack时你可能用的就是命令行方式,以后大部分使用命令行的场景为生产环境打包。

使用方法

方法 1: 
//全局安装
npm install webpack --g
//在命令行使用
$ webpack //<--Generates bundle using webpack.config.js

方法 2 :
//本地安装并保存在package.json中
npm install webpack --save
//在scripts中添加
"scripts": {
 "build": "webpack --config webpack.config.prod.js -p",
 ...
 }
//按如下方式运行
"npm run build"

webpack-dev-server(适用于开发环境构建)

webpack-dev-server是一个基于Expressnode服务器,默认使用8080端口。这个方式的优势是它提供了浏览器热加载(Hot Module Replacement)。
使用方法

方法一:
//全局安装
npm install webpack-dev-server -g
//在命令行使用
$ webpack-dev-server --inline --hot

方法二:
//添加到package.json中
"script": {
     "start": "webpack-dev-server --inline --hot",
     ...
}
//在命令行使用
$ npm start

在浏览器中打开
http://localhost:8080/

webpackwebpack-dev-server选项

须要注意的一点是,像inlinehot这些选项,只有webpack-dev-server有;而另外一些好比hide-modules是单独为webpack命令行方式提供的选项。

webpack-dev-server参数

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中添加以下代码:
package.json
{
    "script": {
        "start": "webpack-dev-server --hot --inline"
    }
}
注意 不要同时设置 devServerhot: trueCLI--hot

"hot" Vs "inline"

inline模式会触发页面的动态重载(live reloading);hot模式会触发页面的热加载(hot Module Replacement),这种模式只重载页面中变化了的部分。若是同时设置了inlinehotwebpack-dev-server会先尝试HMR,若是HMR失败了,则重载整个页面。

//当代码发生变化时,如下3种方式都会从新打包,可是:

//1. 不会重载页面
$ webpack-dev-server

//2. 会重载整个页面
$ webpack-dev-server --inline

//3. HMR, 若失败则加载整个页面
$ webpack-dev-server --inline --hot

entry(String Vs Array Vs Object)

entry指出了打包入口文件,支持字符串,数组对象三种形式。这三种形式有何区别呢?

若是为单一入口文件,也就是说入口文件只有一个,那这三种方式会获得相同的结果。

entryArray

如有多个入口文件,且彼此独立,那么可使用数组方式。好比入口文件为a.jsb.js,使用数组方式会将b.js的内容追加到bundle.js的内容后。
一个很常见的场景就是在html文件加入统计代码,好比googleAnalytics.js,就能够用数组的方式告知webpack将其打包到bundle.js末尾,以下:
图片描述

entryObject

这种方式主要针对多页面应用(指包含多个html文件)。这种方式可使webpack根据这个对象一次就打包出多个文件。
以下这种配置能够打包出两个js文件:indexEntry.jsprofileEntry.js,能够分别在index.htmlprofile.html中引入。

{
    entry: {
        "indexEntry": "./public/src/index.js",
        "profileEntry": "./public/src/profile.js"
    },
    output: {
        path: "/dist",
        filename: "[name].js"  //indexEntry.js & profileEntry.js
    }
}

使用方法

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

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

注: outputname对应的是entry中的属性名。

entry — 结合使用arrayobject

能够在object内部再使用array方式。好比以下配置:

{
    entry: {
        "vendor": ['jquery', 'analytics.js', 'optimizely.js'],
        "index": "./public/src/index.js",
        "profile": "./public/src/profile.js"
    },
    
    output: {
        path: "/dist",
        filename: "[name].js"  //vendor.js, index.js & profile.js
    }
}

outputpathpublicPath

output设定了打包生成文件的路径。它有两个属性pathpublicPath
path告知webpack将打包生成后的文件存储于什么路径,好比咱们但愿将文件打包到dist文件夹下,只需设置path/dist便可;publicPath用于在生产环境打包时更新文件(包括csshtml)中的url
以下配置:

//开发环境config
entry: __dirname + "/app/main.js",

output: {
    path: __dirname + "/public",
    
    //开发环境中不须要使用publicPath, 除非你的静态资源好比图片等没有存储在本地开发环境。
    //publicPath: "http://mycdn.com",
    filename: "bundle.js"
}

//生产环境config
entry: __dirname + "/app/main.js",

output: {
    path: __dirname + "/public",
    
    //publicPath: 一些插件(url-loader, file-loader, HtmlWebpackPlugin等)
    //在生成图片,样式表等的url路径时会用到该配置
    //好比:
    //.image {
    //    background-image: url('./test/png');
    //}
    //按以下配置打包后会变成:
    //.image {
    //    background-image: url('http://mycdn.com/test.png');
    //}
    publicPath: "http://mycdn.com/",
    filename: "bundle.js"
}

举个例子,在你的css文件中,用到了./test.png这个url去加载本地的图片。可是在生产环境中,这张图片test.png会存储在cdn服务器上。这样若是仍是用./test.png就会访问不到该图片,必须把文件中全部的url手动改为cdn的路径才能在生产环境使用。

webpack为咱们提供的publicPath这个属性使咱们能够很方便地处理这类问题。只须要将publicPath设置为生产环境的路径,这些识别publicPath的插件,好比url-loader,就会自动为咱们处理好url。以下图所示:

图片描述

//开发环境,server和image都在本地
.image {
    background-image: url('./test.png');
}

//生产环境, server在HeroKu服务器上,而image在cdn上
.image {
    background-image: url('https://someCDN/test.png');
}

译者注: publicPath还用于指定在使用webpack-dev-server时,如何访问其暂存于内存中的打包后的文件。)

加载器和链式加载器(Loaders And Chaining Loaders

加载器是一些node模块,能够加载(load)或者引入(import)各类类型的文件使其转化成浏览器支持的文件格式,包括js,css等等。
好比:可使用babel-loader将使用ES6写的js文件转换为浏览器支持的ES5格式。以下:

module {
    loaders: [
        {
            test: /\.js$/,  //检测js文件,若是是,则对其使用loader处理
            exclude: /node_modules/,  //不对node_modules下文件处理
            loader: 'babel'   //使用babel (对babel-loader的简写)
        }
    ]
}

链式加载(从右至左)

对同一种类型的文件能够链式运用多个加载器。链式调用为从右向左,经过!分割加载器。
举例:咱们有一个css文件myCSSFile.css,咱们想将这个文件中的内容转换成<style>CSS content</style>的形式插入到咱们的html页面中。可使用两个加载器css-loaderstyle-loader来达成以上目的:

modules: {
    loaders: [
        {
            test: /\.css$/,
            loader: 'style!css'  //style-loader!css-loader的简写
        }
    ]
}

以下展现了其原理:
图片
一、webpack查找模块中依赖的css文件。也就是说,webpack会检查js文件中是否引用了myCSSFile.css。若是找到了依赖,webpack会先用css-loader对其进行处理。
二、css-loader会加载全部的css和这个css的依赖(好比@import otherCSS),并将css的内容处理为JSON数据格式。而后将结果传给style-loader进行处理。
三、style-loader会对接收到的json数据进行处理,并将其处理为style标签——<style>CSS contents</style>,而后插入到html页面中。

加载器可配置

能够向loaders传递各类参数进行配置。
在如下这个例子中,咱们对url-loader进行了配置:小于1024字节的图片将会被转为为base64格式,而大于1024字节的图片仍是使用图片url。有两种方式进行配置:

//方式1 使用'?'
{
    test: /\.png$/,
    loader: "url-loader?limit=1024"
}

//方式2 使用'query'属性
{
    test: /\.png$/,
    loader: "url-loader",
    query: {limit: 1024}
}

.babelrc文件

使用babel-loader的话,须要配置presets才能正确转化,包括将es6转换为es5,将JSX转为js。能够经过以下方式设置参数

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

可是在不少项目中babel的配置可能会比较大,因此能够单独在babel的配置文件.babelrc中配置。若是有.babelrcbabel-loader会自动加载该文件。
以下:

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

//.babelrc
{
    "presets": ["react", "es2015"]
}

插件(Plugins

插件是一些node模块,能够对生成的打包文件进行处理。
好比,uglifyJSPlugin插件能够对打包后获得的bundle.js进行压缩处理,减少文件体积。
extract-text-webpack-plugin运用了css-loaderstyle-loader将全部的css统一处理并根据结果生成一个单独的css文件(style.css),将文件连接插入到html文件中。

//webpack.config.js
//获取全部的css文件,并将其内容整合,生成一个单独的css文件'style.css'
var ETP = require('extract-text-webpack-plugin');

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

plugins: [
    new ExtractTextPlugin("style.css")
]

注:
若是你只是想使用内联css样式,在html页面中加入style标签,能够只用cssstyle加载器。以下:

module: {
    loaders: [
        {
            test: /\.css$/,
            loader: 'style!css'
        }
    ]
}

加载器和插件(Loaders Vs Plugins)

能够看到,加载器做用于单独的文件,在bundle生成以前完成;
插件做用于bundlechunk,一般是在bundle生成过程的最后进行。一些插件好比commonsChunksPlugins甚至会影响bundle如何生成。(译者注:该插件用于提取出各个模块中引用的相同模块,下篇文章code splitting中会详细说明)

文件后缀处理(Resolving File Extensions)

不少webpack配置文件中都包含一个resolve extensions的属性,其中包含一个空字符串。这个空字符串就是用于正确加载不含后缀的文件的。好比:require('./myJSFile')import myJSFile from './myJSFile'

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

注: 翻译水平有限,若有问题还但愿你们能不吝赐教,但愿和你们共同进步。

(本文完)

相关文章
相关标签/搜索