[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 =============================================');