try webpack

前不久把 sf 前端的构建工具进行了改进和优化,用上了目前很是火的 webpack 、babel 和 es6 等等新技术。javascript

历史

sf 前端的构建工具最先使用的是当时很是流行的 grunt,接下来是 gulp,而后就是如今的 webpack。css

构建工具比较

构建工具 Browserify Grunt Gulp Webpack
描述 browser-side require() the node way The JavaScript Task Runner The streaming build system Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jade, coffee, css, less, ... and your custom stuff.
关键词 browser, require, commonjs, commonj-esque, bundle, npm, javascript task, async, cli, minify, uglify, build, lodash, unit, test, qunit, nodeunit, server, init, scaffold, make, jake, tool
做者 James Halliday Grunt Development Team Fractal Tobias Koppers @sokra
连接  Homepage Bug Report Github  Homepage Bug Report Github  Homepage Bug Report Github  Homepage Bug Report Github
比较
Licenses MIT MIT MIT MIT
Created 5 years ago (Feb, 2011) 5 years ago (Jan, 2012) 3 years ago (Jul, 2013) 4 years ago (Mar, 2012)
版本数量 459 56 63 416
版本周期 every 4 days every a month every 18 days every 4 days
依赖数 46 16 13 15

什么选择了它,它解决了什么

  • gulphtml

    • 配置简单,插件丰富,上手快前端

    • 解决了 grunt 配置繁琐,不易维护的问题,经过插件扩展进一步提升了开发效率vue

  • webpackjava

    • 解决模块自由引入,打包node

webpack 解决了 sf 后台存在的历史问题,提升了开发效率react

  1. html 模板、js 模块的自由引入,再也不依赖配置表jquery

  2. 每次添加新模块,再也不须要加入一堆的 script 标签webpack

  3. 代码压缩合并速度提高

  4. coffee 换成了 js(es6)

Webpack 增长了项目开发的灵活性,优化了性能

  1. 提取公用 js 代码

  2. 只要有 loader ,能够自由使用多种语言

  3. 支持按需加载

怎么用

gulp 入门 

去年在 SF 技术分享会安利过 gulp building width gulp

咱们是怎么用 webpack 的

配置 webpack须要建一个 webpack.config.js 文件。建议搭配 webpack-dev-server 使用:webpack doc webpack-dev-server

咱们的配置文件大概长这个样子。下面来分析一下。

var webpack = require('webpack');

var reusePlugin = new webpack.ProvidePlugin({
  $: 'jquery',
 jQuery: 'jquery',
 _:'underscore',
 'window.jQuery': 'jquery',
 'root.jQuery': 'jquery'
});

var config = {
  context: __dirname + '/src',
  devtool: 'source-map',
  entry:{
    'chart':[
      './chart/ChartCtrl.js',
      './chart/ChartDirective.js',
    ],
    'log':'./log/ctrl.js',
    'dashboard':'./dashboard/ctrl.js',
    'operation':'./operation/ctrl.js'
  },
  output:{
    path: __dirname + '/dist',
    filename:'./[name]/ctrl.js',
    publicPath:'/static/dist/',
  },

  devServer: {
    hot: true,
    port: 3333,
    proxy: {
        '*': {
            target: 'http://xxadmin.domain.com',
            secure: false,
            changeOrigin: true
        },
    },
    },
  module:{
    loaders:[
      {
      test: /\.js$/,
      exclude: /(node_modules|bower_components|3rd)/,
      loader: 'babel', // 'babel-loader' is also a legal name to reference
      query: {
        presets: ['es2015']
      }
    },
    {
        test: /\.html$/,
        loader: "html"
    },
    {
        test:   /\.css$/,
        exclude: /(node_modules|bower_components|3rd)/,
        loader: "style-loader!css-loader"
    },
    {
        test: /\.scss$/,
        loaders: ["style", "css", "sass"]
     },
     { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }
    ]
  },
  plugins: [
    reusePlugin,
 ]
};

module.exports = config;
entry

处理的入口,配置须要处理的 js。entry 有三种写法,每一个入口称为一个chunk。

  • 字符串  entry: "./index/index.js"
    配置模块会被解析为模块,并在启动时加载。默认 chunk 名为 main, 具体打包文件名可在 output 中配置。

  • 数组  entry: ['./src/mod1.js', [...,] './src/index.js']
    全部的模块会在启动时 按照配置顺序 加载,合并到最后一个模块会被导出。默认 chunk 名也是 main。

  • 对象  entry: {index: '...', login : [...] }
    传入Object,则会生成多个入口打包文件, key 是 chunk 名,value能够是字符串,也但是数组。

很明显咱们采用的是第三种。

output

设置入口配置的文件的输出规则,经过output对象实现

  • output.path:输出文件路径,一般设置为 __dirname + ‘/build’

  • output.filename:输出文件名称,有下面列出的四种可选的变量,filename 配置能够是这几种的任意一种或多种的组合

    • [id] chunk的id

    • [name] chunk名

    • [hash] 编译哈希值

    • [chunkhash] chunk的hash值

  • output.publicPath:设置为想要的资源访问路径。通常使用 webpack-dev-server 时,则须要经过相似 http://localhost:8080/asstes/index-1.js 来访问资源,若是没有设置,则默认从站点根目录加载。

web_modules

有些时候,咱们用到的第三方库并无采用 CommonJS 或 AMD 规范。这样咱们没法经过 require() 来引用这些库。

Webpack 给出了解决方案,在项目根目录下,建立一个叫作 web_modules 的文件夹,将须要用到的第三方库存放到里面,就能够在逻辑代码中使用 require(‘xx-lib.js’) 来引用并使用了。

去除多个文件中的频繁依赖

当咱们常用React、jQuery等外部第三方库的时候,一般在每一个业务逻辑JS中都会遇到这些库。

如咱们须要在各个文件中都是有jQuery的$对象,所以咱们须要在每一个用到jQuery的JS文件的头部经过require('jquery')来依赖jQuery。 这样作很是繁琐且重复。

webpack提供了咱们一种比较高效的方法,咱们能够经过在配置文件中配置使用到的变量名,那么webpack会自动分析,而且在编译时帮咱们完成这些依赖的引入。

这样,咱们在JS中,就不须要引入jQuery等经常使用模块了,直接使用配置的这些变量,webpack就会自动引入配置的库。

new webpack.ProvidePlugin({
  $: 'jquery',
 jQuery: 'jquery',
 _:'underscore',
 'window.jQuery': 'jquery',
 'root.jQuery': 'jquery'
});
功能标识(Feature flags)

项目中有些代码咱们只为在开发环境(例如日志)或者是内部测试环境(例如那些没有发布的新功能)中使用,又不想让这些调试内容在发布的时候泄露出去,那就须要引入下面这些魔法全局变量(magic globals):

if (__DEV__) {
  console.warn('Extra logging');
}
// ...
if (__PRERELEASE__) {
  showSecretFeature();
}

同时还要在webpack.config.js中配置这些变量,使得 webpack 可以识别他们。

// webpack.config.js

// definePlugin 会把定义的string 变量插入到Js代码中。
var definePlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
  __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [definePlugin]
};

配置完成后,就可使用 BUILD_DEV=1 BUILD_PRERELEASE=1 webpack来打包代码了。 值得注意的是,webpack -p 会删除全部无做用代码,也就是说那些包裹在这些全局变量下的代码块都会被删除,这样就能保证这些代码不会因发布上线而泄露。

异步加载

虽然CommonJS是同步加载的,可是webpack也提供了异步加载的方式。这对于单页应用中使用的客户端路由很是有用。当真正路由到了某个页面的时候,它的代码才会被加载下来。

指定你要异步加载的 拆分点。看下面的例子

if (window.location.pathname === '/feed') {
  showLoadingState();
  require.ensure([], function() { // 这个语法痕奇怪,可是仍是能够起做用的
    hideLoadingState();
    require('./feed').show(); // 当这个函数被调用的时候,此模块是必定已经被同步加载下来了
  });
} else if (window.location.pathname === '/profile') {
  showLoadingState();
  require.ensure([], function() {
    hideLoadingState();
    require('./profile').show();
  });
}

剩下的事就能够交给webpack,它会为你生成并加载这些额外的 chunk 文件。

简化执行代码

咱们能够在package.json中事先定义好命令:

"scripts": {    
  "dev": "BUILD_DEV=1 webpack-dev-server --progress --colors",    
  "build": "BUILD_PRERELEASE=1 webpack -p"  }

那么就能够避免输入冗长的命令了

  • 开发时输入 npm run dev

  • 发布时输入 npm run build

合并优化公共代码

项目中,对于一些经常使用的组件,站点公用模块常常须要与其余逻辑分开,而后合并到同一个文件,以便于长时间的缓存。要实现这一功能,配置参照:

var webpack            = require('webpack');

var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
...

entry: {

   a: './index/a.js',

   b: './idnex/b.js',

   c: './index/c.js',

   d: './index/d.js'

},

...

plugins: [

   new CommonsChunkPlugin('part1.js', ['a', 'b']),

   new CommonsChunkPlugin('common.js', ['part1', 'c'])

]

简单的状况下能够这样写 newwebpack.optimize.CommonsChunkPlugin('common.js’);,这样就会提取全部模块的通用代码到 common.js。

devtool 调试

能够经过在配置中加入devtool项,选择预设调试工具来提升代码调试质量和效率:

  • eval – 每一个模块采用eval和 //@ sourceURL 来执行

  • source-map – sourceMap是发散的,和output.sourceMapFilename协调使用

  • hidden-source-map – 和source-map相似,可是不会添加一个打包文件的尾部添加引用注释

  • inline-source-map – SourceMap以DataUrl的方式插入打包文件的尾部

  • eval-source-map – 每一个模块以eval方式执行而且SourceMap以DataUrl的方式添加进eval

  • cheap-source-map – 去除column-mappings的SourceMap, 来自于loader中的内容不会被使用。

  • cheap-module-source-map – 去除column-mappings的SourceMap, 来自于loader中的SourceMaps被简化为单个mapping文件

devtool 构建速度 再次构建速度 支持发布版 质量
eval +++ +++ no 生成代码
cheap-eval-source-map + ++ no 转换代码(lines only)
cheap-source-map + o yes 转换代码(lines only)
cheap-module-eval-source-map o ++ no 源代码 (lines only)
cheap-module-source-map o yes 源代码(lines only)
eval-source-map + no 源代码
source-map yes 源代码
loader

来自官方文档:

Loaders allow you to preprocess files as you require() or “load” them. Loaders are kind of like “tasks” are in other build tools, and provide a powerful way to handle frontend build steps. Loaders can transform files from a different language like CoffeeScript to JavaScript, or inline images as data URLs. Loaders even allow you to do things like require() css files right in your JavaScript!

module.exports = {

entry: ["./global.js" , "./app.js"],

output: {

filename: "bundle.js"

},
module: {

loaders: [

  {
      test: /\.es6$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
    query: {
          presets: ['react', 'es2015']
      }
  }
]

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

咱们的第一个loader 添加了3个键,下面分别作下解释。

  1. test — 一个正则表达式,测试什么样的文件类型能够经过loader去执行。上面的例子意思是仅后缀为.es6的文件经过。

  2. exclude — 表示loader 应该忽略/不包含的文件/文件路径。例如 node_modules 文件夹.

  3. loader —表示咱们正在使用的loader 名称 (babel-loader).

  4. query — 你能够传递一些选项参数到loader,写法相似一个 query string 或者像上面的例子那样使用 query 属性。

  5. presets —让咱们可以使用早先安装好的 react 和 es2015 的 presets。

another config example

var webpack            = require('webpack');

var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;

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

//自定义"魔力"变量

var definePlugin = new webpack.DefinePlugin({

    __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'false')),

    __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))

});

module.exports = {

    //上下文

    context: __dirname + '/src',

    //配置入口

    entry: {

        a: './view/index/index.js',

        b: './view/index/b.js',

        vender: ['./view/index/c.js', './view/index/d.js']

    },

    //配置输出

    output: {

        path: __dirname + '/build/',

        filename: '[name].js?[hash]',

        publicPath: '/assets/',

        sourceMapFilename: '[file].map'

    },

    devtool: '#source-map',

    //模块

    module: {

        loaders: [

            {

                //处理javascript

                test: /\.js$/,

                exclude: /node_modules/,

                loader: 'babel'

            }, {

                test: /\.css$/,

                loader: ExtractTextPlugin.extract(

                    "style-loader",

                    "css-loader?sourceMap"

                )

            }, {

                test: /\.less$/,

                loader: ExtractTextPlugin.extract(

                    "style-loader",

                    "css-loader!less-loader"

                )

            }, {

                test: /\.(png|jpg)$/,

                loader: 'url-loader?limit=1024'

            }, {

                //处理vue

                test: /\.vue$/,

                loader: 'vue-loader'

            },

            {

                test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,

                loader: 'url?limit=10000&minetype=application/font-woff'

            },

            {

                test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,

                loader: 'url?limit=10&minetype=application/font-woff'

            },

            {

                test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,

                loader: 'url?limit=10&minetype=application/octet-stream'

            },

            {

                test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,

                loader: 'file'

            },

            {

                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,

                loader: 'url?limit=10&minetype=image/svg+xml'

            }

        ]

    },

    plugins: [

        //公用模块

        new CommonsChunkPlugin('common.js', ['a', 'b']),

        //设置抽出css文件名

        new ExtractTextPlugin("css/[name].css?[hash]-[chunkhash]-[contenthash]-[name]", {

            disable: false,

            allChunks: true

        }),

        //定义全局变量

        definePlugin,

        //设置此处,则在JS中不用相似require('./base')引入基础模块, 只要直接使用Base变量便可

        //此处一般可用作,对经常使用组件,库的提早设置

        new webpack.ProvidePlugin({

            Moment: 'moment', //直接从node_modules中获取

            Base: '../../base/index.js' //从文件中获取

        })

    ],

    //添加了此项,则代表从外部引入,内部不会打包合并进去

    externals: {

        jquery: 'window.jQuery',

        react: 'window.React',

        //...

    }

};

总结

工欲善其事,必先利其器。

参考资料

  1. webpack经常使用配置总结

  2. Webpack 入门指迷

  3. Webpack官方文档

  4. webpack-howto

  5. beginner-s-guide-to-webpack

  6. compare browserify,grunt,gulp,webpack

相关文章
相关标签/搜索