Webpack

更优秀的前端模块依赖管理工具

材主 2016.2

  • Webpack简介
  • Webpack常用功能
  • Webpack安装使用
  • Webpack模块编写
  • 项目迁移方案
  • 附录

What is Webpack?

网上介绍

webpack是近期最火的一款模块加载器打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理。

require("./lib.js");
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");

require

模块依赖,一招搞定

在 Webpack 当中, 所有的资源都被当作是模块

加载器

对应各种不同文件类型的资源,

Webpack 有对应的模块 loader

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'}
    ]
}

Webpack常用功能

JS里:CSS及图片引用

require('./bootstrap.css');
require('./myapp.less');

var img = document.createElement('img');
img.src = require('./glyph.png');
  • Synchronous
  • CSS和LESS会被打包到JS
  • 图片可能被转化成 base64 格式的 dataUrl
module: {
    loaders: [
        //图片文件使用 url-loader 来处理,小于8kb的直接转为base64
        { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
    ]
}

LESS/CSS里:图片引用

根据配置“url-loader?limit=xxx”来决定把图片转换成base64还是图片链接形式引用。

background-image: url("./logo.png");
module: {
    loaders: [
        //图片文件使用 url-loader 来处理,小于8kb的直接转为base64
        { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
    ]
}

CSS能单独打包吗?

"extract-text-webpack-plugin"

插件

只需两步

1. 插件安装

npm install extract-text-webpack-plugin --save

2. 配置文件webpack.config.js

var ExtractTextPlugin = require("extract-text-webpack-plugin");

...

plugins: [
    // 目标文件名规则[name].css
    new ExtractTextPlugin('[name].css', {allChunks: true})
],
module: {
    loaders: [
        { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
        { test: /\.less$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
    ]
},

公共代码自动抽离

// 分析以下模块的共用代码, 单独打一个包到common.js
var commonsPlugin = 
    new webpack.optimize.CommonsChunkPlugin(/*chunkName=*/'common', /*filename=*/'common.js');

plugins: [
    commonsPlugin
],
<script src="build/common.js"></script>

记得要在HTML手动引入common.js

A.js,B.js

a.js,b.js, common.js

HTML自动引用JS/CSS

插件"html-webpack-plugin"

$: npm install html-webpack-plugin --save

参考:https://www.npmjs.com/package/html-webpack-plugin-extra-files

var HtmlWebpackPlugin = require('html-webpack-plugin'); // Html文件处理

module.exports = {

	……

	plugins: [
		/**
		 * HTML文件编译,自动引用JS/CSS
		 * 
		 * filename - 输出文件名,相对路径output.path
		 * template - HTML模板,相对配置文件目录
		 * chunks - 只包含指定的文件(打包后输出的JS/CSS),不指定的话,它会包含生成的所有js和css文件
		 * excludeChunks - 排除指定的文件(打包后输出的JS/CSS),比如:excludeChunks: ['dev-helper']
		 * hash
		 */
		new HtmlWebpackPlugin({filename: 'views/list.html', template: 'src/modules/app/list/index.html', chunks: ['common', 'List'], hash: true}),
		new HtmlWebpackPlugin({filename: 'views/detail.html', template: 'src/modules/app/detail/index.html', chunks: ['common', 'Detail'], hash: true})
	],
};

功能开关

有些代码我们只想在开发环境使用(比如log)

全局变量插件:webpack.DefinePlugin

if(__DEV__) {
    console.log('run in dev.');
}

JS中使用全局变量:

module.exports = {
  plugins: [
    // 全局变量
    new webpack.DefinePlugin({
      // __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')), //通过环境变量设置
      __DEV__: JSON.stringify(JSON.parse('true')), // 开发调试时把它改为true
      __HELLO__: JSON.stringify('hello world')
    })
  ]
};

配置文件:

注意:webpack -p 会执行 uglify dead-code elimination, 任何这种代码都会被剔除, 所以你不用担心秘密功能泄漏.

异步加载

require.ensure

语法:

require.ensure(dependencies: String[],

                           callback: function([require]),

                           [chunkName: String])

与require AMD类似,也是在需要的时候才会加载相应的模块。但不同的是,require.ensure在模块被下载下来后(模块还没被执行)便立即执行回调函数.

另外require.ensure可以指定构建后chunk名,如果之前已有require.ensure指定了该名称,webpack会将这些模块统一合并到一个模块集里。

// 异步加载
if(i < 0) {
    require.ensure([], function() {
        require('a.js');
    });
}

简单例子

定义异步加载文件名字

output: {
    chunkFilename: "[id].chunk.[hash].js"
},

生成的异步文件引用逻辑自动包含在源目标JS中,不用手动引用,所以以上文件名随便怎么定义都不影响。

file-loader

图片加载器url-loader其实是对file-loader的一个封装

loaders: [
    {
        test: /\.(png|jpg|gif)$/,
        loader: 'url-loader',
        query: {
            name: '[path][name].[ext]?[hash:8]',
            limit: 8192
        }
    }
]

如果文件超出体积, 就给一个这样规则的文件名

ES6支持

module: {
    loaders: [
        {
            test: /\.js$/, loader: 'babel-loader', // ES6
            exclude: /(node_modules|bower_components|ppaweb\\libs\\webpack)/
        },
    ]
},

babel-loader

Alias

项目迁移更方便

resolve: {
    alias: {
        // 从module调用webpack上的公共lib库路径简写
        'lib0': '../../../ppaweb/libs/webpack',

        // 从module的子文件夹调用webpack上的公共lib库路径简写
        'lib1': '../../../../ppaweb/libs/webpack', 

        // 从module的两层子文件夹调用webpack上的公共lib库路径简写
        'lib2': '../../../../../ppaweb/libs/webpack' 
    },
    // 现在可以写 require('file') 代替 require('file.coffee')
    extensions: ['', '.js', '.json', '.coffee']
}

安装使用

安装

#首先确保机子上已安装node.js,然后通过npm安装webpack
$: npm install webpack -g

启动

# 切换到有 webpack.config.js 的目录然后运行
$: webpack     // 执行一次开发的编译
$: webpack -p  // 针对发布环境编译(压缩代码)
$: webpack -w  // 进行开发过程持续的增量编译(飞快地!)
$: webpack -d  // 生成map映射文件,告知哪些模块被最终打包到哪里了
$: webpack --config XXX.js   //使用另一份配置文件(比如webpack.config2.js)来打包

配置文件

webpack.config.js

通用配置文件例子,能满足日常开发需要

// webpack.config.js
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'common', /* filename= */'common.js'); // 分析以下模块的共用代码, 单独打一个包到common.js
var ExtractTextPlugin = require("extract-text-webpack-plugin"); // 单独打包CSS
var HtmlWebpackPlugin = require('html-webpack-plugin'); // Html文件处理

module.exports = {
    entry: {
        Detail: './modules/app/detail.js',
        Home: './modules/app/home.js'
    },
    output: {
        path: './build', // This is where images & js will go
        //publicPath: 'http://m.pp.cn/ppaweb/test/build/', // This is used to generate URLs to e.g. images
        publicPath: '/ppaweb/example/build/', // This is used to generate URLs to e.g. images
        filename: '[name].js',
        chunkFilename: "[id].chunk.js?[hash:8]"
    },
    plugins: [
        commonsPlugin,
        new ExtractTextPlugin('[name].css', {allChunks: true}), // 单独打包CSS

        // 全局变量
        new webpack.DefinePlugin({
            //__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV||'false')) //通过环境变量设置
            __DEV__: 'false' // 开发调试时把它改为true
        }),

        /**
         * HTML文件编译,自动引用JS/CSS
         * 
         * filename - 输出文件名,相对路径output.path
         * template - HTML模板,相对配置文件目录
         * chunks - 只包含指定的文件(打包后输出的JS/CSS),不指定的话,它会包含生成的所有js和css文件
         * excludeChunks - 排除指定的文件(打包后输出的JS/CSS),比如:excludeChunks: ['dev-helper']
         * hash
         */
        new HtmlWebpackPlugin({filename: 'views/home.html', template: 'views/home.html', chunks: ['common', 'Home'], hash: true}),
        new HtmlWebpackPlugin({filename: 'views/detail.html', template: 'views/detail.html', chunks: ['common', 'Detail'], hash: true})
    ],

    module: {
        loaders: [
            {
                test: /\.js$/, loader: 'babel-loader', // ES6
                exclude: /(node_modules|bower_components|ppaweb\\libs\\webpack)/
            },
            // CSS,LESS打包进JS
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
            // CSS,LESS单独打包
            //{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
            //{ test: /\.less$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },

            { test: /\.tpl$/, loader: 'ejs'}, // artTemplate/ejs 's tpl
            {
                test: /\.(png|jpg|gif)$/,
                loader: 'url-loader',
                query: {
                    name: '[path][name].[ext]?[hash:8]',
                    limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
                }
            }
        ]
    },
    resolve: {
        alias: {
            'lib0': '../../../ppaweb/libs/webpack', // 从module调用webpack上的公共lib库路径简写
            'lib1': '../../../../ppaweb/libs/webpack', // 从module的子文件夹调用webpack上的公共lib库路径简写
            'lib2': '../../../../../ppaweb/libs/webpack' // 从module的两层子文件夹调用webpack上的公共lib库路径简写
        },
        // 现在可以写 require('file') 代替 require('file.coffee')
        extensions: ['', '.js', '.json', '.coffee']
    }
};

Webpack模块编写

Webpack模块框架

参考:[androidppweb]ppaweb/libs/webpack/*

// var $ = require('zepto');
// require('./index.less');

!(function () {

    var module1 = (function () {
        var _e = {};

        _e.test = function () {
            // do something here
        };

        return _e;
    })();


    window.module1 = module1;

    try {
        module.exports = module1;
    } catch (e) {}

})();

目录规范

project-name
  ├─ debug    (调试目录,如有需要)
  ├─ release  (发布目录)
  ├─ src      (开发目录)
      ├─ modules            (模块化资源目录,JS/LESS/图片等资源分散到各模块中)
      ├─ views              (非模块化资源目录,HTML/公共图片等)
      ├─ server             (nodejs服务器,如有需要)
  ├─ package.json       (nodejs包描述文件)
  ├─ webpack.config.js  (webpack配置文件,记得配置output目录为"./release")
  ├─ LICENSE            (协议说明文件,如有需要)
  └─ README.md          (项目说明文件)

以下目录规范,公供参考

例子原型

https://github.com/diamont1001/webpack-demo

项目迁移方案

一般一个页面(HTML)对应一个入口文件

1.入口文件

entry: {
    Detail: './modules/app/detail.js',
    Soft: './modules/app/soft/soft.js',
    Search: './modules/app/search/search.js',
    Activity: './modules/app/activity/activity.js',
    Category: './modules/app/category.js',
    Update: './modules/app/update/update.js',
    IgnoreUpdate: './modules/app/update/ignore-update.js',
    Home: './modules/app/home.js'
},

配置文件

2.文件引用

<script src="lib1.js"></script>
<script src="lib2.js"></script>
<script src="index.js"></script>
require('lib1.js');
require('lib2.js');
require('index.js');

简单粗暴型

温文儒雅型

<script src="lib1.js"></script>
<script src="lib2.js"></script>
<script src="index.js"></script>
#loading.js
require('loading.less');
#index.js
var loading = require('loading.js'),
    header = require('header.js');
#header.js
require('header.less');

模块抽离

3.优化

  • common.js
  • CSS单独打包
  • 异步加载
  • ……

成功栗子

PP浏览器插件

FIS PURE => Webpack

专题模板化

Grunt => Webpack

Webpack+React

Git官网:

https://webpack.github.io/

 

入门指引(含例子webpack+react):

https://github.com/petehunt/webpack-howto

 

还是看原作者的分享PPT吧^_^:

http://sokra.github.io/slides/webpack#1

 

官方文档

网上文章教程

Q&A

Q. HTML里引用JS能自动生成访问后缀吗?比如a.js?2016

A. 插件html-webpack-plugin

Thanks

Webpack

By 材主

Webpack

更优秀的前端模块依赖管理工具

  • 4,004