傻瓜版Webpack2.x版本示例项目
- 下载webpack-demo
项目目录
├── app │ ├── app.js │ ├── app.scss │ ├── config.json │ ├── demo.js │ ├── demo.tmpl.html │ ├── greeter.js │ ├── icon.png │ ├── index.temp.html │ ├── jquery-1.11.3.min.js │ ├── main.js │ ├── main.scss ├── package.json └── webpack.config.js
- 运行
- npm install webpack -g 安装webpack,运行webpack -h查看是否安装成功
- npm install webpack-dev-server -g 安装webpack-dev-server,可以通过一个socket.io服务实时监听文件的变化并自动刷新页面
- 安装第三方npm模块npm install
- 在根目录下运行webpack-dev-server命令开启本地服务
- 直接打开http://localhost:8080/测试
package.json
{ "name": "camera", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --progress --colors" }, "author": "vcxiaohan", "license": "ISC", "devDependencies": { "css-loader": "^0.26.4", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "^0.10.1", "html-webpack-plugin": "^2.28.0", "json-loader": "^0.5.4", "node-sass": "^4.5.0", "sass-loader": "^6.0.3", "style-loader": "^0.13.2", "url-loader": "^0.5.8", "webpack": "^2.2.1" } }
webpack.config.js
var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 必须配合html-webpack-plugin才能生效 module.exports = { devtool: 'source-map', //配置生成Source Maps,以便报错时能定位到具体的行列 entry: { // 入口文件配置项 main: __dirname + "/app/main.js", // js文件标识,传入HtmlWebpackPlugin的chunks参数位置,以便生成多个页面时能引用不同的js文件 demo: __dirname + "/app/demo.js", // }, output: { // 输出文件配置项 path: __dirname + "/public", // 打包后的文件存放的地方 filename: "[name].js" // 打包后输出文件的文件名 }, module: { // 在配置文件里添加JSON loader loaders: [{ test: /\.json$/, loader: "json-loader" // webpack2.x 版本不能省略loader后缀 }, { test: /\.scss$/, loader: ExtractTextPlugin.extract({ // extract-text-webpack-plugin2.x 版本写法 fallback: 'style-loader', use: 'css-loader!sass-loader' }) }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=10&name=[hash].[ext]' // url-loader需要配合file-loader使用才能生效,否则读取大图片的时候会报错 }, ] }, plugins: [ // 插件配置项 new HtmlWebpackPlugin({ // 生成多页面 template: __dirname + "/app/index.tmpl.html", // 使用模板 filename: 'index.html', // 输出的html文件名 chunks: ['main', 'common'], // 引用的js文件标识,必须要引入CommonsChunkPlugin独立出来的common文件 }), new HtmlWebpackPlugin({ // 生成多页面 template: __dirname + "/app/demo.tmpl.html", filename: 'demo.html', chunks: ['demo'], }), new webpack.BannerPlugin("by vcxiaohan"), //new webpack.optimize.UglifyJsPlugin(),// 压缩js new ExtractTextPlugin("[name].css"), // 提取css样式为单独的文件 new webpack.optimize.CommonsChunkPlugin({ // 把main文件标识的公共部分提取出来独立成common文件 name: 'common', chunks: ['main'] }), ], devServer: { // 本地服务配置项 contentBase: "./public", // 自定义本地服务器基本目录 inline: true, // 实时刷新 proxy: { // 代理 '/': { target: 'http://v4.faqrobot.net', changeOrigin: true // 解决跨域代理 } } } }
更新于2018-8-10
webpack3.12.0版本示例项目
- package.json
{
"name": "vue",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "LAPTOP-ASHTD2FO\\Think <vcxiaohan@foxmail.com>",
"private": true,
"scripts": {
"dll": "webpack --config webpack.dll.conf.js",
"dev": "webpack-dev-server --config webpack.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
"build": "webpack --config webpack.conf.js"
},
"dependencies": {
"jquery": "^3.3.1",
"vue": "^2.5.2",
"vue-router": "^3.0.1"
},
"devDependencies": {
"add-asset-html-webpack-plugin": "^2.1.3",
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^21.0.2",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^2.0.1",
"chromedriver": "^2.27.2",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.0.1",
"cross-spawn": "^5.0.1",
"css-hot-loader": "^1.4.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"happypack": "^5.0.0",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.0.4",
"jest-serializer-vue": "^0.3.0",
"nightwatch": "^0.9.12",
"node-notifier": "^5.1.2",
"node-sass": "^4.9.3",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"purify-css": "^1.2.5",
"purifycss-webpack": "^0.7.0",
"rimraf": "^2.6.0",
"sass-loader": "^7.1.0",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"style-loader": "^0.22.0",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.12.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
- webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const pkg = require('./package.json')
const library = '[name]_lib'
module.exports = {
entry: {
vendors: Object.keys(pkg.dependencies),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].dll.js',
chunkFilename: '[name].chunk.js',
// DllPlugin插件需要该配置参数,用来生成manifest的映射名称
library
},
plugins: [
// 该插件将第三方静态资源单独打包处理,避免我们在各种环境中重复打包永远不会变化的文件,优化效果非常明显(需要配合webpack内置插件DllReferencePlugin一起使用)
new webpack.DllPlugin({
// 打包后的文件路径
path: path.resolve(__dirname, 'dist/[name]-manifest.json'),
name: library
}),
// 压缩js文件,不要使用webpack内置的压缩插件,因为其版本低,缓存和多线程压缩配置项都不生效,这里使用独立的压缩插件还是有效的,同时该插件在设置babel的module为false时默认开启js tree shaking功能
new UglifyJsPlugin({
// 开启缓存
cache: true,
// 开启多线程并行处理
parallel: true,
}),
]
}
- webpack.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const glob = require('glob')
const PurifyCSSPlugin = require('purifycss-webpack')
const os = require('os')
const HappyPack = require('happypack')
const threadPool = HappyPack.ThreadPool({ size: os.cpus().length })
// 样式loader配置文件,因为有多处用到,故抽离成方法以便调用,一个ExtractTextPlugin实例是allInOne模式的,即所有的css合并成一个css文件,如果想抽离成多个文件,需要生成多个ExtractTextPlugin实例分别调用,如果想再优化的话,可以配合DllPlugin插件(这里我偷个懒,没有分开配置css和scss,请看我对css文件的正则,导致css和scss都会走这个方法,其实css是不需要sass-loader的)
function generateLoaders() {
return ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader'],
})
}
module.exports = {
// source map配置项,由于打包后的bundle是一个文件,出现js错误后,不能定位到具体的模块文件,开启此项,帮我们解决此问题,但是不同的source map模式处理速度不同,建议不同环境,选择不同的模式
// devtool: 'cheap-module-eval-source-map',
entry: {
app: './app.js',
// app2: './app2.js',
// app3: './app3.js',
// vendors: ['vue', 'jquery'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]-[hash:7].bundle.js',
chunkFilename: '[name].chunk.js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
// vue别名,我们在使用npm安装vue的时候,会安装vue的各种版本,适用于不同的环境,由于在webpack2.x版本中我们都是使用ES6 Module规范,如import、export等关键字,所以我们需要配置能遵循ES6 Module规范的那个vue版本
'vue$': 'vue/dist/vue.esm.js',
// 文件夹前缀别名,在引用某个模块时,我们需要写出绝对路径,很麻烦,有了这个别名,我们可以把绝对路径的前缀使用@来代替
'@': path.resolve(__dirname, './'),
}
},
module: {
rules: [
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
loaders: {
/* // 这里可以省略,因为vue文件中的js会自动根据.babelrc文件的配置去处理
js: {
loader: 'babel-loader',
// options: '.babelrc文件内容拷贝至此亦可',
}, */
css: generateLoaders(),
scss: generateLoaders(),
}
}
},
}, {
test: /\.js$/,
/* use: {
// 将相应的id任务的loader交给happypack去处理,由于happypack可以开启多个线程并行处理,可以优化速度,个人测试,效果并不理想,慎用,网上也看到很多人说使用happypack没有效果,个人测试用例:对element-ui、jquery、vue、vue-router共4个模块大约900kb,使用babel编译,正常编译和使用happypack编译时间上并无什么差别(happypack显示开启了4个线程,偶尔时间反而还会更多)
loader: 'happypack/loader?id=babel',
}, */
use: {
loader: 'babel-loader',
/* // 使用babel-loader插件时,如果我们想要编译es6为低版本浏览器能兼容的es5的话,我们需要为该loader配置一些参数,而且每个需要用到babel-loader插件的地方,都要在相应的地方写配置参数,比如我们要编译.vue文件中的<script>块的es6语法时,需要用到vue-loader,在vue-loader配置参数中我们依然要写一遍babel-loader的配置参数,显得很繁琐,因此我们把要写的配置参数统统提取出来放到.babelrc文件中,所有要用到babel的地方,在编译时都会自动去查询有没有这个文件存在,而且庆幸的是这个文件支持JSON5格式,即我们在这个文件中可以随心所欲的写单引号、加单行、多行注释、所有的键都可以不加双引号,就像写一个普通的js对象一样,而不是写一个格式要求严格的JSON文件
options: '.babelrc文件内容拷贝至此亦可', */
},
exclude: '/node_modules'
}, {// 开启模块热更新后,style-loader会自动帮我们处理css模块并实现模块局部更新,但是由于我们又使用了ExtractTextPlugin插件抽离css样式为单独的文件,所以这时css热更新会失效(该插件缺少处理热更新的api),当然我们可以不用ExtractTextPlugin插件,这样一来我们引入的样式文件都是以style标签的形式插入html中,很不友好,为此网上有人专门写了一个CSS Hot Loader插件,使我们在使用ExtractTextPlugin插件的同时又能实现css模块热更新功能(总结:开发模式只需要热更新,提高编译速度,推荐使用写法1,生产模式只需要提取css文件,推荐使用写法2)
test: /\.s?css$/,
/* // css loader写法1(此写法需主动把plugins配置插件处的ExtractTextPlugin的代码注释)
// 最简单的处理css的loader写法,hot为true时,css热更新生效,但是不能抽离css为单独的样式文件
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
], */
// css loader写法2
// hot为true时,css热更新失效,但是能抽离css为单独的样式文件
use: generateLoaders(),
/* // css loader写法3(个人测试结果,该css-hot-loader的css热更新效果并不理想,慎用)
// hot为true时,css热更新生效,同时能抽离css为单独的样式文件
use: ['css-hot-loader'].concat(generateLoaders()), */
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
// 当图片文件的大小比limit大时,会把文件交给file-loader去处理,反之,则自己处理并返回该文件的base64字符串,我个人认为base64的利大于弊,在这里我设为-1,表示任何时候都不使用base64(base64优点:减少http请求、字符串可以使用gzip;缺点:一般图片转为base64后,总字符串的体积会变大,不可读,并且显得冗余,还会增加CSSOM解析时间,我比较倾向的是使用cssSprites的方案来合并小图标)
limit: -1,
name: './dist/img/[name].[hash:7].[ext]',
}
},
]
},
plugins: [
/* // 打包结果可视化分析,该插件会自动打开一个窗口,展示打包后的结果,我们可以根据最终打包的模块依赖做分析、优化工作
new BundleAnalyzerPlugin(), */
// 该插件将第三方静态资源单独打包处理,避免我们在各种环境中重复打包永远不会变化的文件,优化效果非常明显(需要配合webpack内置插件DllPlugin一起使用)
new webpack.DllReferencePlugin({
manifest: require('./dist/vendors-manifest.json')
}),
// 生成页面插件,可以自定义模板、页面title,写多个即表示生成多页面
new HtmlWebpackPlugin({
template: 'test.html',
}),
// 生成script标签引用指定路径,以实现自动添加静态资源到页面中,由于我们使用了DllReferencePlugin,所以我们需要手动把打包后的资源路径添加到页面中,而该插件帮我们自动完成
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, 'dist/*.dll.js'),
includeSourcemap: false,
// 文件名带有随机hash值,防止缓存,不能设置hash值长度,真的难受
hash: true,
}),
// 提取css样式为单独的文件
new ExtractTextPlugin({
// 多入口时,必需使用name或contenthash值来确定打包出的css文件名,这样提取出来的多个css文件就会以不同的名字命名,避免相互覆盖
filename: '[name]-[contenthash:7].css',
allChunks: true,
}),
/* // 清理无用css,即css tree shaking,比如dom树中压根没有这个节点,但是我们却写了这个节点的样式,这个插件会帮我们除掉没有用的css样式,个人测试,效果并不理想,慎用
new PurifyCSSPlugin({
// 个人测试,只使用bootstrap的分页样式(其他的模块也有问题),进行shaking后,css文件确实小了很多,但是展示的分页样式跟用shaking之前差了很多
paths: glob.sync(path.resolve(__dirname, './test.html')),
}), */
// 提取公用代码,webpack的一个优化点,比如某个模块我们在不同的入口分别引入了1次,那么最终打包的多个bundle中都会有该模块的代码,增大了代码体积,使用此插件我们可以从多个bundle中提取公用的模块,减少代码体积,但是注意提取过程需要时间,所以建议开发环境下开启
new webpack.optimize.CommonsChunkPlugin({
// 提取出来的公用代码的名称
name: 'common',
// 配置几个入口都引入该模块时才去提取,该值比较重要,决定了最终提取的公用代码的体积和无用代码量(无用代码量:如果你设为1,那么只要某个模块被某个入口引入了1次,该模块就会被提取,但是其他的入口可能并不需要这个模块,那么就会成为无用代码)
minChunks: 2,
}),
// 注入全局变量,相当于使用别名来代替每次手动引入某个模块,如以下,我们在文件中就不需要再手动书写'import $ from \'jquery\''了
new webpack.ProvidePlugin({
$: 'jquery',
}),
/* // 该插件让webpack能同时使用多个线程去处理loader,个人测试,效果并不理想,慎用
new HappyPack({
// 处理的任务id
id: 'babel',
loaders: [{
loader: 'babel-loader',
options: {
presets: [
['babel-preset-env', {
targets: {
browsers: ['last 2 versions']
}
}]
]
}
}],
// 使用的线程数
threadPool
}), */
// 在打包前对指定文件夹清理,由于我们加了hash值来命名文件,每次文件改动后,会重新生成新的文件,所以需要定时清理文件夹,开发环境不需要使用
new CleanWebpackPlugin(['dist'], {
// 不写,也不会报错
root: __dirname,
// 清理时,排除某些文件,比如我们的第三方依赖库,从来没有改变过,所以不需要清理
exclude: ['vendors.dll.js', 'vendors-manifest.json'],
}),
// hot为true时,必须使用该插件热更新才能生效
new webpack.HotModuleReplacementPlugin(),
// 热更新时,输出本次热更新的模块相对路径
new webpack.NamedModulesPlugin(),
],
devServer: {// 当使用webpack-dev-server模块来启动项目时,该配置项生效,会开启一个node服务器
// 开启热更新
hot: true,
// 路由错误处理,当我们访问一个不存在的路由时,express会报404,该插件在出现此情况时,重定向到某个自己写的页面,更友好的提醒开发者
historyApiFallback: {
rewrites: [
{
from: /./,
to: '/404.html'
}
]
}
}
}
细节整理
- extract-text-webpack-plugin需要配合html-webpack-plugin使用才能生效
- url-loader需要配合file-loader使用才能生效,否则读取大图片的时候会报错(最新版的url-loader已经内置了file-loader)
- CommonsChunkPlugin提取出来的文件,必须要在HtmlWebpackPlugin里面引用,否则报错webpackJsonp is not defined?
- Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,不过webpack把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。
参考文档
- 入门Webpack,看这篇就够了
- webpack官方文档
- Webpack傻瓜式指南(一)
- Webpack傻瓜指南(二)开发和部署技巧
- 用webpack-dev-server开发时代理,决解开发时跨域问题
- 用webpack的CommonsChunkPlugin提取公共代码的3种方式
- webpackJsonp is not defined?
- Vue + Webpack + Vue-loader 系列教程(1)功能介绍篇
- devDependencies和dependencies的区别
- babel的polyfill和runtime的区别
- webpack 配合babel 将es6转成es5 超简单实例
- Babel 全家桶
- webpack之babel插件困惑解疑
- Webpack2 升级指南和特性摘要
- html-webpack-plugin用法全解
- html-webpack-plugin 中使用 title选项设置模版中的值无效
- webpack多页应用架构系列(七):开发环境、生产环境傻傻分不清楚?