Webpack 指南(整理 草稿)

l1xnan

此篇终结,请直接看 Webpack2 中文版
此篇终结,请直接看 Webpack2 中文版
此篇终结,请直接看 Webpack2 中文版

基础

安装

首先要安装 Node.js, Node.js 自带了软件包管理器 npm。用 npm 全局安装 Webpack:

$ npm install webpack -g

通常我们会将 Webpack 安装到项目的依赖中,这样就可以使用项目本地版本的 Webpack。

# 进入项目目录,初始化,创建 package.json。
# 若存在 package.json 文件,则不运行。
$ npm init
# 确定已经有 package.json
# 安装 webpack 依赖
$ npm install webpack --save-dev

如果需要使用 Webpack 开发工具,要单独安装:

$ npm install webpack-dev-server --save-dev

使用

首先创建一个静态页面 index.html 和一个 JS 入口文件 entry.js

<!-- index.html -->
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <script src="bundle.js"></script>
</body>
</html>
// entry.js
document.write('It works.')

然后编译 entry.js 并打包到 bundle.js

$ webpack entry.js bundle.js

用浏览器打开 index.html 将会看到

It works. 

最终目录结构如下:

.
├── entry.js
├── index.html
├── package.json
├── node_modules

接下来添加一个模块 module.js 并修改入口 entry.js

// module.js
module.exports = 'It works from module.js.'
// entry.js
document.write('It works.')
document.write(require('./module.js')) // 添加模块

重新打包 webpack entry.js bundle.js 后刷新页面看到变化

It works.It works from module.js.

最终目录结构如下:

.
├── bundle.js
├── entry.js
├── index.html
├── module.js
├── package.json
├── node_modules

进阶

使用 Loader

Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。
Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,我们就可以通过 require 来加载任何类型的模块或文件,比如 CoffeeScript、 JSX、 LESS 或图片。

先来看看 loader 有哪些特性?

  • Loader 可以通过管道方式链式调用,每个 loader 可以把资源转换成任意格式并传递给下一个 loader ,但是最后一个 loader 必须返回 JavaScript。

  • Loader 可以同步或异步执行。

  • Loader 运行在 node.js 环境中,所以可以做任何可能的事情。

  • Loader 可以接受参数,以此来传递配置项给 loader。

  • Loader 可以通过文件扩展名(或正则表达式)绑定给不同类型的文件。

  • Loader 可以通过 npm 发布和安装。

  • 除了通过 package.json 的 main 指定,通常的模块也可以导出一个 loader 来使用。

  • Loader 可以访问配置。

  • 插件可以让 loader 拥有更多特性。

  • Loader 可以分发出附加的任意文件。

Loader 本身也是运行在 node.js 环境中的 JavaScript 模块,它通常会返回一个函数。大多数情况下,我们通过 npm 来管理 loader,但是你也可以在项目中自己写 loader 模块。

按照惯例,而非必须,loader 一般以 xxx-loader 的方式命名,xxx 代表了这个 loader 要做的转换功能,比如 json-loader

除了npm安装模块的时候以外,在任何场景下,loader名字都是可以简写的。例如:安装时必须用全名,即:npm install json-loader,而在引用 loader 的时候可以使用全名 json-loader,也可以使用短名 json。这个命名规则和搜索优先级顺序在 webpack 的 resolveLoader.moduleTemplates api 中定义。

Default: ["*-webpack-loader", "*-web-loader", "*-loader", "*"]

Loader 可以在 require() 引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。

loader是可以串联使用的,也就是说,一个文件可以先经过A-loader再经过B-loader最后再经过C-loader处理。而在经过所有的loader处理之前,webpack会先取到文件内容交给第一个loader。

接上一节的例子,我们要在页面中引入一个 CSS 文件 style.css,首先将 style.css 也看成是一个模块,然后用 css-loader 来读取处理(路径处理、import处理等),然后经过 style-loader 处理(包装成JS文件,运行的时候直接将样式插入DOM中)。

/* style.css */
body { background: yellow; }

修改 entry.js

// entry.js
require("!style!css!./style.css") // 载入 style.css
document.write('It works.')
document.write(require('./module.js'))

安装 loader:

# css-loader:读取 css 文件
# style-loader:将 css 文件插入页面
$ npm install css-loader style-loader --save-dev

重新编译打包,刷新页面,就可以看到黄色的页面背景了。

如果每次 require CSS 文件的时候都要写 loader 前缀,是一件很繁琐的事情。我们可以根据模块类型(扩展名)来自动绑定需要的 loader。

entry.js 中的 require("!style!css!./style.css") 修改为 require("./style.css") ,然后执行:

$ webpack entry.js bundle.js --module-bind 'css=style!css'

显然,这两种使用 loader 的方式,效果是一样的。
最终的目录结构如下:

.
├── bundle.js
├── entry.js
├── index.html
├── module.js
├── node_modules
├── package.json
├── style.css

loader还可以接受参数,不同的参数可以让loader有不同的行为(前提是loader确实支持不同的行为),具体每个loader支持什么样的参数可以参考loader的文档。loader的使用有三种方法,分别是:

  • 在require中显式指定,如:

  • 在命令行中指定,如:$ webpack entry.js output.js --module-bind 'css=style!css'

  • 在配置项(webpack.config.js)中指定,如:

第一种显式指定,即在 JS 文件中指定:

require('style!css!./style.css');`

第二种在命令行中指定参数的用法用得较少,可以这样写:

$ webpack --module-bind jade --module-bind 'css=style!css'

使用 --module-bind 指定loader,如果后缀和loader一样,直接写就好了,比如jade表示.jade文件用jade-loader处理,如果不一样,则需要显示指定,如 css=style!css 表示分别使用 css-loaderstyle-loader 处理 .css 文件。

第三种在配置项中指定是最灵活的方式,它的指定方式是这样:

module: {
    // loaders是一个数组,每个元素都用来指定loader
    loaders: [{
        test: /\.jade$/,    //test值为正则表达式,当文件路径匹配时启用
        loader: 'jade',    //指定使用什么loader,可以用字符串,也可以用数组
        exclude: /regexp/, //可以使用exclude来排除一部分文件

        //可以使用query来指定参数,也可以在loader中用和require一样的用法指定参数,如`jade?p1=1`
        query: {
            p1:'1'
        }
    },
    {
        test: /\.css$/,
        loader: 'style!css'    //loader可以和require用法一样串联
    },
    {
        test: /\.css$/,
        loaders: ['style', 'css']    //也可以用数组指定loader
    }]
}

注意: 用数组指定串联loader时,配置文件中要写 loaders,而非 loader

配置文件

Webpack 在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的 webpack.config.js 文件,这个文件是一个 node.js 模块,返回一个 json 格式的配置信息对象,或者通过 --config 选项来指定配置文件。

继续我们的案例,创建配置文件 webpack.config.js

var webpack = require("webpack")
module.exports = {
    entry: './entry.js',
    output: {
        path: __dirname,
        filename: "bundle.js"
    },
    module: {
        loaders: [
            { test: /\.css$/, loader: 'style!css' }
        ]
    }
}

同时简化 entry.js 中的 style.css 加载方式:

require('./style.css')

最后运行 webpack,可以看到 webpack 通过配置文件执行的结果和上一节通过命令行 webpack entry.js bundle.js --module-bind 'css=style!css' 执行的结果是一样的。

插件

插件可以完成更多 loader 不能完成的功能。
插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。
Webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。
接下来,我们利用一个最简单的 BannerPlugin 内置插件来实践插件的配置和运行,这个插件的作用是给输出的文件头部添加注释信息。

修改 webpack.config.js,添加 plugins:

var webpack = require('webpack')

module.exports = {
  entry: './entry.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {test: /\.css$/, loader: 'style!css'}
    ]
  },
  plugins: [
    new webpack.BannerPlugin('This file is created by zhaoda')
  ]
}

然后运行 webpack,打开 bundle.js,可以看到文件头部出现了我们指定的注释信息:

/*! This file is created by zhaoda */
/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
// 后面代码省略

参数详解

entry

entry参数定义了打包后的入口文件,可以是个字符串或数组或者是对象;如果是数组,数组中的所有文件会打包生成一个filename文件;如果是对象,可以将不同的文件构建成不同的文件:

{
    entry: {
        page1: "./page1",
        //支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
        page2: ["./entry1", "./entry2"],
        
        // 如果想保持目录结构,则直接按照目录结构命名
       'subapp1/page': './app/subapp1/page.js',
       'subapp2/page': './app/subapp2/page.js',
    },
    output: {
        path: "dist/js/page",
        publicPath: "/output/",
        filename: "[name].bundle.js"
    }
}

该段代码最终会在 ./dist/js/page 文件夹下生成如下结构:

│  page1.bundle.js 
│  page2.bundle.js
│
├─subapp1
│      page.bundle.js
│      
└─ssubapp2
       page.bundle.js

保持目录结构命名的方式,在构架大型应用中非常有用。

output

output参数是个对象,定义了输出文件的位置及名字:

output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: 'http://localhost:3000/static/',
    filename: "js/[name].bundle.js"
}
  • path: 打包文件存放的绝对路径

  • publicPath: 网站运行时的访问路径

  • filename:打包后的文件名

当我们在entry中定义构建多个文件时,filename可以对应的更改为[name].js用于定义不同文件构建后的名字。
如下 'http://localhost:3000/static/' 一般我们做调试时的路径,如果我们要在网页中引用 js 文件,html 文件中的路径写为:http://localhost:3000/static/js/<name>.bundle.js
<publicPath>+<filename>

module

在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上:

module: {
        //加载器配置
        loaders: [
            //.css 文件使用 style-loader 和 css-loader 来处理
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            //.js 文件使用 jsx-loader 来编译处理
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            //.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            //图片文件使用 url-loader 来处理,小于8kb的直接转为base64
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    }
字段 说明
test 表示匹配的资源类型
loaderloaders 表示用来加载这种类型的资源的loader
定义loader的串联关系,多个loader之间用“!”连接起来

此外,还可以添加用来定义png、jpg这样的图片资源在小于10k时自动处理为base64图片的加载器:

{ test: /\.(png|jpg)$/,loader: 'url-loader?limit=10000'}

给css和less还有图片添加了loader之后,我们不仅可以像在node中那样 require() js文件了,我们还可以 require() css、less甚至图片文件:

 require('./bootstrap.css');
 require('./myapp.less');
 var img = document.createElement('img');
 img.src = require('./glyph.png');

注意,require() 还支持在资源path前面指定loader,即 require(![loaders list]![source path])形式:

require("!style!css!less!bootstrap/less/bootstrap.less");
// “bootstrap.less”这个资源会先被"less-loader"处理,
// 其结果又会被"css-loader"处理,接着是"style-loader"
// 可类比pipe操作

require() 时指定的loader会覆盖配置文件里对应的loader配置项。

resolve

webpack在构建包的时候会按目录的进行文件的查找,resolve 属性中的 extensions 数组中用于配置程序可以自行补全哪些文件后缀:

 resolve: {
        //查找module的话从这里开始查找
        root: '/pomy/github/flux-example/src', //绝对路径
        //自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
        extensions: ['', '.js', '.json', '.scss'],
        //模块别名定义,方便后续直接引用别名,无须多写长长的地址
        alias: {
            AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }

然后我们想要加载一个js文件时,只要 require('common') 就可以加载 common.js 文件了。
注意一下, extensions 第一个是空字符串! 对应不需要后缀的情况.

plugin

webpack提供了[丰富的组件]用来满足不同的需求,当然我们也可以自行实现一个组件来满足自己的需求:

plugins: [
     //your plugins list
 ]

在webpack中编写js文件时,可以通过require的方式引入其他的静态资源,可通过loader对文件自动解析并打包文件。通常会将js文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时 extract-text-webpack-plugin 插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。

npm install extract-text-webpack-plugin –-save-dev
plugins: [
    new ExtractTextPlugin('styles.css')
]

externals

当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题:

 externals: {
     "jquery": "jQuery"
 }

这样我们就可以放心的在项目中使用这些API了:var jQuery = require(“jquery”);

context

当我们在require一个模块的时候,如果在require中包含变量,像这样:

 require("./mods/" + name + ".js");

那么在编译的时候我们是不能知道具体的模块的。但这个时候,webpack也会为我们做些分析工作:

1.分析目录:’./mods’;
2.提取正则表达式:’/^.*.js$/’;

于是这个时候为了更好地配合wenpack进行编译,我们可以给它指明路径,像在cake-webpack-config中所做的那样(我们在这里先忽略abcoption的作用):

 var currentBase = process.cwd();
 var context = abcOptions.options.context ? abcOptions.options.context : 
 path.isAbsolute(entryDir) ? entryDir : path.join(currentBase, entryDir);

关于 webpack.config.js 更详尽的配置可以参考 Webpack Configuration

开发环境

当项目逐渐变大,webpack 的编译时间会变长,可以通过参数让编译的输出内容带有进度和颜色。

$ webpack --progress --colors

如果不想每次修改模块后都重新编译,那么可以启动监听模式。开启监听模式后,没有变化的模块会在编译后缓存到内存中,而不会每次都被重新编译,所以监听模式的整体速度是很快的。

$ webpack --progress --colors --watch

当然,使用 webpack-dev-server 开发服务是一个更好的选择。它将在 localhost:8080 启动一个 express 静态资源 web 服务器,并且会以监听模式自动运行 webpack,在浏览器打开 http://localhost:8080/http://localhost:8080/webpack... 可以浏览项目中的页面和编译后的资源输出,并且通过一个 socket.io 服务实时监听它们的变化并自动刷新页面。

# 安装
$ npm install webpack-dev-server -g

# 运行
$ webpack-dev-server --progress --colors

React 开发环境的配置

Webpack相关:

$ npm install webpack -g
$ npm install webpack-dev-server -g
# 安装必要的 loader:
## 编译 JSX
$ npm install --save-dev babel-loader
## CSS 文件处理
$ npm install --save-dev css-loader style-loader
## React
$ npm install --save-dev react-hot-loader

Babel 相关:

$ npm install --save-dev babel-core
# 添加 ES6 支持
$ npm install --save-dev babel-preset-es2015
$ npm install --save-dev babel-react
var webpack = require('webpack');
module.exports = {
    entry: [
      'webpack/hot/only-dev-server',
      "./js/app.js"
    ],
    output: {
        path: './build',
        filename: "bundle.js"
    },
    module: {
        loaders: [
            { test: /\.js?$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/ },
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'},
            { test: /\.css$/, loader: "style!css" }
        ]
    },
    resolve:{
        extensions:['','.js','.json']
    },
    plugins: [
      new webpack.NoErrorsPlugin()
    ]
};

参考资料:

webpack-dev-server

webpack-dev-server 是一个基于 Node.js Express 框架的开发服务器,它是一个静态资源 Web 服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。

简单来说,webpack-dev-server就是一个小型的静态文件服务器。使用它,可以为webpack打包生成的资源文件提供Web服务。

webpack-dev-server有两种模式支持自动刷新——iframe模式和inline模式。

iframe模式

在iframe模式下:页面是嵌套在一个iframe下的,在代码发生改动的时候,这个iframe会重新加载。使用iframe模式无需额外的配置,只需在浏览器输入:

http://localhost:8080/webpack-dev-server/index.html

inline模式

在inline模式下:一个小型的webpack-dev-server客户端会作为入口文件打包,这个客户端会在后端代码改变的时候刷新页面。使用inline模式有两种方式:命令行方式和Node.js API。

命令行方式比较简单,只需加入--line选项即可。例如:

webpack-dev-server --inline

使用--inline选项会自动把webpack-dev-server客户端加到webpack的入口文件配置中。
注意:默认配置文件名称为:webpack.config.js,若要更改需要在命令行中指明。例如,

webpack-dev-server --inline --config webpack.config.dev.js。

若用Node.js API方式,因为webpack-dev-server没有inline:true这个配置项,所以需要手动把

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

加到配置文件的入口文件配置处。

模块热替换

webpac-dev-server 支持 Hot Module Replacement,即模块热替换,在前端代码变动的时候无需整个刷新页面,只把变化的部分替换掉。使用HMR功能也有两种方式:命令行方式和Node.js API。

命令行方式同样比较简单,只需加入--line --hot选项。--hot这个选项干了一件事情,它把webpack/hot/dev-server入口点加入到了webpack配置文件中。这时访问浏览器,你会看见控制台的log信息:

[HMR] Waiting for update signal from WDS...
[WDS] Hot Module Replacement enabled.

HMR前缀的信息由webpack/hot/dev-server模块产生,WDS前缀的信息由webpack-dev-server客户端产生。

Node.js API方式需要做三个配置:

  1. webpack/hot/dev-server 加入到webpack配置文件的 entry 项;

  2. new webpack.HotModuleReplacementPlugin() 加入到webpack配置文件的plugins项;

  3. hot:true 加入到 Webpack 配置文件的 webpack-dev-server 的配置项里面。

devServer:{
    hot:true
}

注意:要使HMR功能生效,还需要做一件事情,就是要在应用热替换的模块或者根模块里面加入允许热替换的代码。否则,热替换不会生效,还是会重刷整个页面。

if(module.hot)
    module.hot.accept();

也可以使用一些插件去完成这个工作,例如webpack-module-hot-accept插件。不过,webpack-dev-server HMR结合react-hot-loader使用的时候,react-hot-loader会去做这个工作。
综合上述,使用wepack-dev-server辅助开发,使得开发者在开发前端代码的过程中无需频繁手动刷新页面,使用HMR甚至不用等待页面刷新,确实可以给开发者带来很好的体验。

但是,问题又来了。我要进行前后端联调的时候怎么办呢?毕竟webpack-dev-server只是一个静态文件服务器,不具备动态处理的能力。这个时候就需要将后端服务器与webpack-dev-server结合使用了。webpack-dev-server只用来为webpack打包生成的资源文件提供服务,比如js文件、图片文件、css文件等;后端服务器除提供API接口外,还提供入口HTML。

要将webpack-dev-server与后端服务器结合使用,需要做三件事情。

第一 首页HTML文件是从后端服务器发出的,这时页面的根地址变成了后端服务器地址,怎么使得webpack产生的资源文件在请求资源的时候是向web-dev-server请求而不是后端服务器请求?只需在webpack配置文件中的 output.publicPath 配置项写上绝对URL地址,例如output.publicPath = "http://localhost:8080/assets/"。这时,webpack打包产生的资源文件里面的url地址都会是绝对地址,而不是相对地址。
第二 后端服务器产生的入口HTML文件要向webpack-dev-server请求资源文件,这个简单,只需在HTML文件中加入资源文件的绝对地址,例如:<script src="http://localhost:8080/assets/bundle.js">
第三 要使webpack-dev-server和它的运行时程序连接起来。这个简单,只需要使用iline模式即可。

提取公共代码与压缩

var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
    ... ...
    // plugins 项配置中增加
    plugins: [
    
        ... ...  
        // 提取公共代码
        commonsPlugin,
        //压缩
        new webpack.optimize.UglifyJsPlugin({
                compress: {
                    warnings: false
                }
        })
    ]
}

参见:
WEBPACK DEV SERVER
webpack-dev-server 官方文档
前端模块加载工具——webpack(二)

阅读 9k

流水账
笔记

每天进步一点点……

549 声望
19 粉丝
0 条评论

每天进步一点点……

549 声望
19 粉丝
文章目录
宣传栏