webpack使用

Webpack是一个现代js应用的模块打包机。如果一个文件依赖另一个文件,webpack认为这就存在一个依赖关系。不管另一个文件是什么内容,image,css或js都被当作一个模块。Webpack从entry points开始构建依赖关系图,将应用所需要的所有模块处理成浏览器可识别的格式,再打包成一批(个)文件,将来发送给浏览器。
chunk:多个模块打包之后的代码集合。

使用步骤:

1.全局安装webpack

  npm install -g webpack

  如果没有package.json,使用CLI创建:npm init

2.项目根目录安装webpack

  npm install --save-dev webpack

3.配置webpack.config.js文件,运行webpack命令时不用再输入参数

4.运行webpack

在项目根目录通过CLI调用:$webpack   #必须全局安装,否则需要指定webpack.bat的文件路径
  webpack默认只会在当前shell工作目录找webpack.config配置文件;如果配置文件不在shell当前工作目录,可以通过--config ./file/pathOfConfig 进行传参数。如果没有配置文件,则需要输入webpack <entry> <output>.
  webpack内部自定义了一些参数的映射,运行webpack时可以传入,如--debug映射为debug:true;-p映射为--optimize-minimize --optimize-occurrence-order.
  单双连字符的使用没有特殊规定:Two hyphen-minus characters ( -- ) are used on some programs to specify "long options" where more descriptive option names are used.单连字符取代驼峰命名,用于连接单词或指定很短的参数选项(通常只有一个字母)。

或在Node中调用

var webpack = require('webpack');  //只需要本地安装即可
var config2 = require('./webpack.config');
var compiler = webpack(config2);

webpack的配置文件webpack.config.js 

如果该文件存在,只需要执行CLI命令:$webpack,webpack就会自动读取配置,并输出打包的文件。配置文件是以js文件的形式代替命令行形式参数,是符合commonjs规范的node模块,里面可以有function和require(),只要最后以对象形式输出配置即可。

具体配置信息如下:

devtoolsource-map.生成何种类型的source map,方便打包后的调试。

entry webpack从哪里开始构建整个依赖关系。动态的模块不能是entry point。一般一个HTML页面一个entry point。如果有多个entry point,可以采用对象形式定义。不支持通配符配置。如果确实有需要,用如下sinppet,然后将entries传入webpack.config中,或利用node-glob模块读取文件名。

var glob = require("glob");  // Match files using the patterns the shell uses

entry: glob.sync("./src/scripts/*.js")

output打包后输出的路径(path)和输出的文件名(filename)。可以有多个entry point,但只能有一个output。

如果是多entry,filename需要使用替换保证每个输出文件的名字唯一:

  [name] is replaced by the name of the chunk.

  [hash] is replaced by the hash of the compilation.(compilation对象的hash值,和webpack的compiler环境相关,同一次编译时的值一样)

  [chunkhash] is replaced by the hash of the chunk.(具体模块的hash值,和整个文件内容相关。内容不变,chunkhash值不变)

loaderswebpack将每个资源文件当作一个模块,但是webpack只能处理js。通过loaders将其他格式的资源文件转换成模块后(比如将JSX语言转换成js、将coffeescript转换成js)加入依赖关系中,最后打包输出。需要单独安装包并且在webpack.config.js下的modules关键字下进行配置loaders后才能使用。 loders下的字段:

  test:确定哪些文件将被加载器处理;

  user:使用哪个加载器,”-loader”后缀不能省略;

  include/exclude:必须处理或屏蔽不需要处理的文件(文件夹)(可选);

  options:当前loader需要的特殊配置(可选),如babel-loader的.babelrc配置文件里的信息。webpack2.5之前为query

plugins扩展Webpack功能,会在整个构建过程中生效,执行相关的任务。需要通过require()引入并将插件实例添加到plugins数组中。

resovle通过别名、扩展名、根路径或备用目录等属性决定webpack如何更快找到import/require()的模块。

  resolve.alias:用别的路径或模块替代。把requirejs项目改为webpack项目时可以利用此属性。

  resolve.extensions:通过扩展名组成的数组解析require()的模块文件。比如加载一个coffeeScript文件,需要增加’.coffee’扩展名。若修改后必须增加空字符串为第一个元素。

常用命令:webpack  –watch改动代码后自动打包 

自动刷新页面显示修改后的效果

使用webpack-dev-server模块构建本地服务器:

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

安装完毕之后运行$webpack-dev-server --open

如果报命令不识别的错误,可在package.json的script字段添加("start": "webpack-dev-server --progress  --hot --inline --content-base ./dist"),然后运行$npm start命令;

或再全局安装一次。

压缩bundle.js文件的大小

//会加载整个lodash,所以体积更大
import { concat, sortBy, map, sample } from 'lodash';
//Use relative file paths,只加载需要的函数模块,体积更小
//例如import { Button } from 'antd';使用babel-plugin-import插件==》var _button = require('antd/lib/button');
import concat from 'lodash/concat';
import sortBy from 'lodash/sortBy';
import map from 'lodash/map';
import sample from 'lodash/sample';

生产环境中要删除sourcemap

使用 webpack –p命令构建bundle.js

通过命令webpack --profile --json >> stats.json生成构建过程的文件,在 https://webpack.github.io/analyse上分析哪个模块占用的空间大。或者使用webpack-bundle-analyzer插件,用视图的形式分析bundle.js中的每个模块。

var debug = process.env.NODE_ENV !== 'production'; //是否是开发模式,生产模式时需要去除sourcemap
const webpack = require('webpack'); //访问内置插件
const path = require('path');
var htmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm

const config = {
    devtool: debug ? 'cheap-eval-source-map' : undefined, //开发模式时生成何种类型的源代码映射文件,方便调试打包后的代码
    entry: { //webpack打包的切入点,一般一个页面的业务代码定义一个entry point,key为输出chunk的name
        home: ['./footer.js', './home.js'], //若为[string],则将多个相互独立的文件及其依赖打包成一个chunk
        about: './about.js',
        contact: './contact.js',
        vendor: ['jquery', 'lodash', 'vue']    // 使用commonschunkplugin抽离的公共模块
    },
    output: { //输出配置项
        path: path.resolve(__dirname, 'dist'), //打包后输出的路径
        filename: '[name].[chunkhash:8].js', //打包后输出的文件名,多个entry需要替换
        chunkFilename: '[id].js', //name of non-entry chunk files。浏览器按需加载的chunk
        publicPath: 'http://cdnOfCompany.com/' // 供webpack及插件在生产模式下更新内嵌到css、html文件里的cdn路径
    },
    resolve: {
        extensions: ['', '.js', '.vue'],
        fallback: [path.join(__dirname, '../node_modules')],
        alias: {
            '@': resolve('src'), //别名,js中import时使用,在src和@import中需要加 ~
            'moment': path.join(__dirname, '../node_modules/moment/min/moment-with-locales.min.js'), // 为模块配置别名
        }
    },
    externals: {
        'jquery': 'jQuery' // 希望通过script标签引进的依赖,可以require/import,但不打包进bundle。必须先加载,webpack会以module的形式对其引用。
    },
    module: { //    webpack2.0的字段名做了些修改
        rules: [//每条rule定义对应的module如何生成,是loaders的alias
            {
                test: /\.(js|jsx)$/, //一个匹配loaders所处理的文件的拓展名的正则表达式
                exclude: /(node_modules|bower_components)/,
                use: [   //使用到的loader及其配置
                    {
                        loader: 'babel-loader', //使用的加载器名称,-loader后缀不能省略;也可通过?query设置参数
                        options: { //为当前loader设置的参数,对于babel可以提取出单独放在.babelrc文件中;
                            presets: ['react', 'es2015'],
                            plugins: [require('react-html-attrs')]
                        }
                    }
                ]
            }, {
                test: /\.css$/,
                exclude: /(node_modules)/, //手动添加必须处理(include)或屏蔽不需要处理的文件(文件夹)
                use: [//使用多个loader处理同一源文件,从右往左顺序处理
                    //css-loader将css文件当作一个模块引入当前模块中,类名相同样式也不会冲突;style-loader将当前模块中的样式引入页面的style元素中
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1
                        }
                    }, {
                        loader: 'less-loader',
                        options: {
                            noIeCompat: true
                        }
                    }
                ]
            }, {
                // 解决图片的路径在发布前后不一致的问题(资源打包前在/src/assert和/static中,打包后全部归并到/dist/static目录下)
                //vue-cli中:assert中的资源要经过webpack处理,只能通过相对路径(视为模块依赖)访问;/static中的资源不用经过webpack处理,通过绝对路径访问
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,  // 小于8kb的直接转为base64
                loader: 'url-loader',
                options: {
                    limit: 8192,
                    name: utils.assetsPath('img/[name].[hash:7].[ext]') // 被加载器处理后重写的url路径,打包之后图片也会被复制到dist的该路径中
                }
            }
        ]
    },
    plugins: debug ? [] : [//添加插件实例
        //依据模板,生成最终的html文件。该文件中会生成标签自动引用了打包后的js/css文件,不用手动一个一个修改url。
        new htmlWebpackPlugin({     // 一个单页应用配置一个该实例,多个页面配置多个
            filename: 'index.html', //输出文件的名字
            template: './src/index.tpl', //使用的模板文件
            title: 'this is home page', //传入模版的参数,在模版中可以通过<%=htmlWebpackPlugin.options.title%>获取该值;也可以传递其他自定义的属性供模板使用,如css等静态资源,不会被当做依赖被打包。
            favicon: 'path/to/yourfile.ico', //生成的 html 标签中会包含这样一个 link 标签指向favicon
            inject: 'body', //插入script的位置
            chunks: ['vendor', entry],     //不设置时默认包含entry中所有的chunks(包括CommonsChunkPlugin插件生成的chunk)
            excludeChunks: ['contact', 'other chunk']//排除引入的chunk
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.NoErrorsPlugin(),    //构建过程中发生error时跳过错误继续生成
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.DefinePlugin({      // webpack定义的全局常量,编译后会替换成value对应的文本,减少手动一个修改 去除对warning和一些提示信息的代码
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }
        }),
        // https://github.com/LiPinghai/UglifyJSDocCN/blob/master/README.md
        new webpack.optimize.UglifyJsPlugin({    //简化压缩代码,去除console,warning等语句
            sourcemap: false,
            compress: {
                screw_ie8: true,
                warnings: false,
                drop_debugger: true, // 去除debugger语句
                pure_funcs: ['console.log'], // 发布时不被打包的函数
                dead_code: true    // 去除不被执行的代码
            },
            mangle: {
                screw_ie8: true
            },
            output: {
                comments: false,
                screw_ie8: true
            }
        }),
        // http://webpack.github.io/docs/list-of-plugins.html
        // https://stackoverflow.com/questions/39548175/can-someone-explain-webpacks-commonschunkplugin
        new webpack.optimize.CommonsChunkPlugin({   //把所有入口节点的公共代码提取出来,生成一个chunk(name为common.js)进行复用
            names: ['vendor', 'manifest'],     // 需要抽离的公共模块的名字。配置manifest,其他代码改变但vendor不变化时common.js不变,但manifest的hash值改变(因为含有webpack runtime代码)
            filename: 'commons.js',  // 最后生成公共chunk的文件名,优先于output里的filename配置。不建议固定文件名,如果更新了不方便用户重新下载。
            minChunks: Infinity // with more entries, this ensures that no other module goes into the vendor chunk
        }),
        new webpack.DllReferencePlugin({    // 引用dll包中的模块,在dll包中无法找到时才打包进当前bundle
            context: __dirname,
            manifest: require('./manifest.json')
        }),
        // 复制/static目录中的资源到 dist/static下对应的目录中
        new CopyWebpackPlugin([
            {
                from: path.resolve(__dirname, '../static'),
                to: config.build.assetsSubDirectory,    // dist/static下
                ignore: []
            }
        ])
    ],
    watch: true, //监听源文件的修改,之后recompile,但不刷新页面。为了提高性能,需要将safe write关闭,用save file触发。
    watchOptions: {
        aggregateTimeout: 300, //从修改文件开始到rebuilding的延迟时间,将多个改变积累到一起
        poll: 1000, //Check for changes every second
        ignored: /node_modules///排除监听的目录
    },
    devServer: { //构建本地服务器实时提供服务并刷新,编译后的文件保存在内存中,所以比较快。
        contentBase: './', //本地服务器所提供服务的内容来源
        port: 8080, //监听端口,和编辑器的端口不一样
        colors: true, //终端中输出结果为彩色
        historyApiFallback: true, //所有的跳转将指向index.html
        inline: true //会输出错误
    }
};
module.exports = config;
View Code

 code-spitting

/**每个打包后的bundle都有一个webpack启动函数,负责在客户端启动js;使用commonchunkplugin后runtime函数在最后一个chunk中**/ (function(modules) { // webpackBootstrap 

原因:webpack全部打包进一个文件,只要一修改代码,hash就会改变,客户端只能重新下载无法利用缓存。另外有些模块特定的用户不一定用到,首次访问时全部加载会影响首屏速度。

方法:

1. 分离业务逻辑,使用CommonsChunkPlugin打包第三方类库,注意需要在业务逻辑模块之前下载公共模块,因为webpackBootstrap函数会在公共模块中。

<script src="common.js" charset="utf-8"></script>    // webpackBootstrap
<script src="app.js" charset="utf-8"></script>    // webpackJsonp

2. 动态加载指定模块

import()语法

// other logic
import('./sideAd').then(_ => doSomething()).catch(error => 'An error occurred while loading the component');
require.ensure([dependency],function(require){var footAd = require('./footAd') })
// webpack在静态打包时,会将sideAd模块当做单独的chunk打包,将footAd及其依赖单独打包(被webpackJsonp函数包裹),这样当前模块文件体积就会减小

webpack将import('...')转变成``__webpack_require__.e/* import() */(n).then()``*或者``__webpack_require__.e/* require.ensure */(0).then((function (require) {__webpack_require__(n)}))``的形式,在浏览器中webpackbootstrap会*通过__webpack_require__.e(JSONP方法)动态加载chunkId为n的模块。*
require():在webpack项目中只能静态打包到bundle中。依赖都被加载且都被执行后才执行回调函数。
require.ensure():单独打包,动态加载。依赖被下载后不会被执行,只有在回调函数**再次**使用require(module)后,这个模块才会被执行。

3. 使用DllPlugin和DllReferencePlugin

将经常使用的库文件打包到dll包中,它本身不能运行,是用来给业务代码引用的。分离了第三方库,打包速度更快,而且不受业务模块的chunkhash影响。使用步骤:
a. 使用webpack.DllPlugin插件打包第三方库
b. 在主配置文件中使用webpack.DllReferencePlugin插件引用dll包

4. 对于单页应用,可以使用bundle-loader异步加载各路由对应的组件(懒加载),尽快显示首屏。
5. 在vue-router中,使用``const Foo = () => import('./Foo.vue')``懒加载。

    routes: [
        {
            path: '/',
            name: 'Hello',
            component: () => import('../module/HelloWorld')
        },
        {
            path: '/check',
            name: 'check',
            component: r => require(['@/pages/spa/module/CheckBox'], r)
        }
    ]

 

posted @ 2017-05-06 14:12  开发之路  阅读(6579)  评论(2编辑  收藏  举报