晴明的博客园 GitHub      CodePen      CodeWars     

[web] webpack1 到 webpack2

tree-shaking

只有es6模块才能使用webpack2做静态依赖解析。
因为现在大部分浏览器还不支持es6模块语法,所以我们得下载babel,利用babel对代码进行编译。
正常使用Babel 6来转换,一般设置presets: ['es2015']
但是这种方式使用的 transform-es2015-modules-commonjs插件意味着Babel会将es6模块通过commonJs模块转换输出,然后webpack 2就不能进行tree-shaking分析了。
目前的解决办法是修改babel的设置,或者换用 es2015-webpack 这个预设方案。

resolve配置

webpack1中,文件路径查找的resolve.root属性被删除了,取代的是resolve.module属性。

{
	modules: [path.resolve(__dirname, "app"), "node_modules"]
	// (以前这个选项分散在 `root`、`modulesDirectories` 和 `fallback` 三处。)
	// 模块查找路径:指定解析器查找模块的目录。
	// 相对路径会在每一层父级目录中查找(类似 node_modules)。
	// 绝对路径会直接查找。
	// 将按你指定的顺序查找。

	descriptionFiles: ["package.json", "bower.json"],
	// 描述文件:这些 JSON 文件将在目录中被读取。

	mainFields: ["main", "browser"],
	// 入口字段:在解析一个包目录时,描述文件中的这些字段所指定的文件将被视为包的入口文件。

	mainFiles: ["index"]
	// 入口文件:在解析一个目录时,这些文件将被视为目录的入口文件。

	aliasFields: ["browser"],
	// 别名字段:描述文件中的这些字段提供了该包的别名对照关系。
	// 这些字段的内容是一个对象,每当请求某个键名时,就会映射到对应的键值。

	extensions: [".js", ".json"],
	// 扩展名:在解析一个文件时,将会尝试附加这些文件扩展名。

	enforceExtension: false,
	// 强制使用扩展名:如果值为 false,在解析一个文件时,也会尝试匹配无扩展名的文件。

	moduleExtensions: ["-loader"],
	// 模块后缀名:在解析一个模块名时,将会尝试附加这些后缀名。

	enforceModuleExtension: false,
	// 强制使用模块后缀名:如果值为 false,在解析一个模块名时,也会尝试匹配不包含后缀名的模块。

	alias: {
		jquery: path.resolve(__dirname, "vendor/jquery-2.0.0.js")
	}
	// 另外:在解析一个模块名时,会使用这个别名映射表。
}

Loader 的配置

现在,配置文件所指定的各个 loader 的值只匹配 resourcePath(资源路径),而不是以前的 resource(资源)。
这表示 query string(查询字符串)不再参与匹配。

以前在使用 Bootstrap 时有一个问题,会把 Bootstrap 字体和图片的 test 字段搞复杂——必须把 /\.svg$/ 写成 /\.svg($|\?)/。现在可以直接使用那个简单的形式了。

配置文件所指定的各个 loader 现在是相对于配置文件进行解析的(但如果配置文件指定了 context 选项则以它为准)。在以前,如果某些外部模块是通过 npm link 链接到当前包的,则会产生问题,现在应该都可以解决了。

可以使用以下语法来配置 loader:

loaders: [
	{
		test: /\.css$/,
		loaders: [
			"style-loader",
			{ loader: "css-loader", query: { modules: true } },
			{
				loader: "sass-loader",
				query: {
					includePaths: [
						path.resolve(__dirname, "some-folder")
					]
				}
			}
		]
	}
]

UglifyJsPlugin 将不再把所有 loader 都切到代码压缩模式。debug 选项已经被移除。Loader 不应该再从 Webpack 的配置那里读取自己选项了。取而代之的是,需要通过 LoaderOptionsPlugin 来提供这些选项。

new webpack.LoaderOptionsPlugin({
	test: /\.css$/, // optionally pass test, include and exclude, default affects all loaders
	                // 可以传入 test、include 和 exclude,默认会影响所有的 loader
	minimize: true,
	debug: false,
	options: {
		// pass stuff to the loader
		// 这里的选项会传给 loader
	}
})

ExtractTextPlugin

ExtractTextPlugin需要更新到2.0版本。新老配置方式对比:

    Webpack1 : ExtractTextPlugin.extract('css!postcss!sass')  
    Webpack2: ExtractTextPlugin.extract({  
                        fallbackLoader: "style-loader",  
                        loader: "css-loader?minimize"  
                    })  

CommonsChunkPlugin

CommonsChunkPlugin配置方式变更

    Webpack1:CommonsChunkPlugin('common/common','js/[name].js')  
    Webpack2:CommonsChunkPlugin({name:'commonFile',filename:'js/[name].[hash:8].js' })  

HMR communication

在 Webpack 1 中,更新信号用的是Web Messaging API(postMessage)。
而Webpack 2将使用一个标准的事件触发器来传递事件信号。
这表示WebSocket必须内联到打包文件中。
webpack-dev-server必须使用新版本。

System.import

遵循 ES6 的代码拆分方式
ES6 Loader 规范定义了 System.import 方法,用于在运行时动态加载 ES6 模块。
Webpack 把 System.import 作为拆分点,然后把被请求的模块放入一个单独的 “块”(chunk)中。
System.import 接受模块名作为参数,然后返回一个 Promise。

function onClick() {
	System.import("./module").then(module => {
		module.default;
	}).catch(err => {
		console.log("Chunk loading failed");
	});
}
顺便说个好消息:chunk 加载失败产生的错误现在可以被捕获了。

动态表达式
还可以把一个表达式作为参数传给 System.import。表达式的处理方式类似于 CommonJS(Webpack 为每个可能的文件创建一个独立的上下文)。
System.import 会令每个可能的模块都产生一个独立的 chunk。

function route(path, query) {
	return System.import("./routes/" + path + "/route")
		.then(route => new route.Route(query));
}

// 这会为每种可能的路径组合都创建一个对应的 chunk。

无法探测出接口的使用情况:

  • import * as ...
  • System.import
  • 以 CommonJS 或 AMD 语法调用 ES6 模块

  • Webpack 2 将增加对 ES6 模块的原生支持。这意味着 Webpack 现在可以识别 import 和 export 了,不需要先把它们转换成 CommonJS 模块的格式。
  • webpack.optimize.OccurenceOrderPlugin插件不再需要配置,默认开启。
  • 可以自由地混用ES6和AMD 和 CommonJS三种模块类型(哪怕在是在同一个文件内)。Webpack 在这种情况下的行为跟 Babel 类似。
  • webpack-dev-server 现在在默认情况下就处于内联模式。

参考实例

1

/* global __dirname */

var path = require('path');

var webpack = require('webpack');
var CopyWebpackPlugin = require('copy-webpack-plugin');

var dir_js = path.resolve(__dirname, 'js');
var dir_html = path.resolve(__dirname, 'html');
var dir_build = path.resolve(__dirname, 'build');

module.exports = {
    entry: path.resolve(dir_js, 'main.js'),
    output: {
        path: dir_build,
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: dir_build,
    },
    module: {
        loaders: [
            {
                loader: 'babel-loader',
                test: dir_js,
                query: {
                    //presets: ['es2015'],

                    // All of the plugins of babel-preset-es2015,
                    // minus babel-plugin-transform-es2015-modules-commonjs
                    plugins: [
                        'transform-es2015-template-literals',
                        'transform-es2015-literals',
                        'transform-es2015-function-name',
                        'transform-es2015-arrow-functions',
                        'transform-es2015-block-scoped-functions',
                        'transform-es2015-classes',
                        'transform-es2015-object-super',
                        'transform-es2015-shorthand-properties',
                        'transform-es2015-computed-properties',
                        'transform-es2015-for-of',
                        'transform-es2015-sticky-regex',
                        'transform-es2015-unicode-regex',
                        'check-es2015-constants',
                        'transform-es2015-spread',
                        'transform-es2015-parameters',
                        'transform-es2015-destructuring',
                        'transform-es2015-block-scoping',
                        'transform-es2015-typeof-symbol',
                        ['transform-regenerator', { async: false, asyncGenerators: false }],
                    ],
                },
            }
        ]
    },
    plugins: [
        // Simply copies the files over
        new CopyWebpackPlugin([
            { from: dir_html } // to: output.path
        ]),
        // Avoid publishing files when compilation fails
        new webpack.NoErrorsPlugin()
    ],
    stats: {
        // Nice colored output
        colors: true
    },
    // Create source maps for the bundle
    devtool: 'source-map',
};

2

var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var HtmlResWebpackPlugin = require('html-res-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin-hash');
var path = require('path');
var glob = require('glob');
// 是否发布模式
var isProd = process.env.NODE_ENV;

var projectConfig = require('./project.config.json');

var srcPath = path.resolve(projectConfig.srcPath);

//导出配置
module.exports = {
    //entry配置项的根目录(绝对路径)
    context: srcPath,
    entry: {},
    output: {
        //打包输出文件的路径
        path: path.resolve(projectConfig.buildPath),
        //引用文件的公共目录
        publicPath: projectConfig.publicPath,
        //输出文件名
        filename: isProd ? 'js/[name].[hash:8].js' : 'js/[name].js',
        //公共部分文件名
        chunkFilename: isProd ? 'js/[name].[hash:8].js' : 'js/[name].js'
    },
    plugins: [
        new webpack.DefinePlugin({
            __DEBUG__: !isProd
        }),
        //复制lib到发布目录
        new CopyWebpackPlugin([{
            from: projectConfig.libsPath,
            to: 'js/' + projectConfig.libsPath
        }], {
            namePattern: '[name].js'
        })
    ],
    resolve: {
        //查找依赖的的路径,webpack2新增配置项目,webpack1中对应的root
        modules: [srcPath, './node_modules'],
        extensions: ['.js', '.css', '.json', '.html'],
        //别名,配置后可以通过别名导入模块
        alias: {
            css: path.join(path.resolve(process.cwd(), "./src/css"))
        }
    },
    //配置外部文件,单独引入不打包
    externals: {
        "zepto":"$"
    },
    //开发配置,设置source map更易调试,发布模式不使用
    devtool: isProd ? '' : 'cheap-source-map',
    //server配置
    devServer: {
        headers: {
            "Cache-Control": "no-cache"
        },
      /*stats: {
            colors: true
        },*/
        stats: 'errors-only',
        host: '0.0.0.0',
        port: 8000
    },
    module: {
        noParse: [/\.html$/],
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue'
            }, {
                //关键代码,只有去掉babel的cjs模块,才能做tree shaking打包
                test: /\.js$/,
                loader:'babel',
                query: isProd ? {
                    cacheDirectory: true,
                    plugins: [
                        'transform-es2015-template-literals',
                        'transform-es2015-literals',
                        'transform-es2015-function-name',
                        'transform-es2015-arrow-functions',
                        'transform-es2015-block-scoped-functions',
                        'transform-es2015-classes',
                        'transform-es2015-object-super',
                        'transform-es2015-shorthand-properties',
                        'transform-es2015-computed-properties',
                        'transform-es2015-for-of',
                        'transform-es2015-sticky-regex',
                        'transform-es2015-unicode-regex',
                        'check-es2015-constants',
                        'transform-es2015-spread',
                        'transform-es2015-parameters',
                        'transform-es2015-destructuring',
                        'transform-es2015-block-scoping',
                        'transform-es2015-typeof-symbol',
                        ['transform-regenerator', {async: false, asyncGenerators: false}]
                    ]
                } : {
                    cacheDirectory: true,
                    presets: ['es2015'],
                    plugins: ['transform-runtime']
                },
                exclude: /node_modules/
            }, {
                test: /\.css$/,
                loader: isProd ? ExtractTextPlugin.extract({
                    fallbackLoader: "style-loader",
                    loader: "css-loader?minimize"
                }) : 'style!css'
            }, {
                test: /\.(jpe?g|png|gif|svg)(\?]?.*)?$/i,
                loaders: isProd ?
                    ['url?limit=100&name=[path][name].[hash:8]_.[ext]'] : ['file?name=[path][name].[ext]']
            }]
    }
};

//发布时加载插件
if (isProd) {
    //module.exports.output.publicPath = '/';
    module.exports.plugins.push(
        new webpack.NoErrorsPlugin(),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({
            sourceMap: true,
            compress: {
                screw_ie8: false,
                unused: true,
                warnings: false
            },
            output: {
                max_line_len: 300
            },
            comments: false
        }),
        //因为js都打在页面上,所以抽离公共代码没什么意义
        //new CommonsChunkPlugin({name:'commonFile', filename:isProd ? 'js/[name].[hash:8].js' : 'js/[name].js'}),
        new ExtractTextPlugin('css/[name].[contenthash:8].css')
    )
}

function log(msg) {
    console.log(' ' + msg);
}

log('\r\n =============================================');
log('查找到page入口文件:');
var entryConfig = {
    inline: { // inline or not for index chunk
        js: isProd ? true : false,
        css: isProd ? true : false
    }
};

//查找entry入口
glob.sync(projectConfig.entrys, {
    cwd: srcPath
}).forEach(function (entryPath) {
    var aliaName = path.basename(entryPath, '.js');
    var entryName = path.dirname(entryPath) + '/' + aliaName;
    if (!module.exports.resolve.alias[aliaName]) {
        module.exports.entry[entryName] = [entryPath];
        var chunks = {
            'js/lib/version': entryConfig
        };
        log(entryName)
        chunks[entryName] = entryConfig;
        //加载html生成插件
        module.exports.plugins.push(new HtmlResWebpackPlugin({
            filename: 'html/' + path.dirname(entryPath).split('/')[1]+'/'+aliaName + '.html',
            template: projectConfig.htmlMap[path.dirname(entryPath)+'/'+aliaName] || path.join('src/html/', aliaName + '/' + aliaName + '.html'),
            htmlMinify: isProd ? {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            } : false,
            chunks: chunks
        }));
    }
});
//加载VUE 文件
glob.sync(projectConfig.vueEntrys, {
    cwd: srcPath
}).forEach(function (entryPath) {
    var aliaName = path.basename(entryPath, '.js');
    var entryName = path.dirname(entryPath) + '/' + aliaName;
    if (!module.exports.resolve.alias[aliaName]) {
        module.exports.entry[entryName] = [entryPath];
        var chunks = {
            'js/lib/version': entryConfig
        };
        chunks[entryName] = entryConfig;
        //加载html生成插件
        module.exports.plugins.push(new HtmlResWebpackPlugin({
            filename: 'html/vue/' + aliaName + '/'+aliaName + '.html',
            template: projectConfig.htmlMap[path.dirname(entryPath)+'/'+aliaName] || path.join('src/vue/', aliaName + '/' + aliaName + '.html'),
            htmlMinify: isProd ? {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            } : false,
          //  chunks:chunks
        }));
    }
});
log('\r\n =============================================');
posted @ 2017-04-26 19:11  晴明桑  阅读(1124)  评论(0编辑  收藏  举报