webpack构建vue项目

最近公司要求用vue重构项目,还涉及到模块化开发,于是乎,我专门花了几天的时间研究了一下webpack这个目前来看比较热门的模块加载兼打包工具,发现上手并不是很容易,现将总结的一些有关配置的心得分享出来,欢迎大神来拍砖。。。

一、新建一个项目目录,cd /d 定位进去,然后输入npm init,会提示你填写一些项目的信息,一直回车默认就好了,或者直接执行npm init -y 直接跳过,这样就在项目目录下生成了一个package.json文件。

二、接下来就是通过npm安装项目依赖项,命令行输入:npm install babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-0 babel-runtime vue-loader vue-html-loader vue-hot-reload-api css-loader style-loader webpack webpack-dev-server --save-dev ,继续输入npm install vue@^1.0.26 --save 。

这里注意的几个点如下:

1.需要安装的依赖项视具体的项目需求来定,我只是安了几个必需的,后期会再加;

2.输入之后如果一直报错或者光标一直在转动,要么是npm版本太低(需要3+),要么将npm改成cnpm,如果没有安装淘宝NPM镜像,可以先输入npm install -g cnpm --registry=https://registry.npm.taobao.org,接着输入cnpm -v查看是否安装完成,然后就可以使用cnpm来代替npm;

3.可以先修改package.json文件中的devDependencies和dependencies,然后再输入npm install进行一次性安装(偷懒的做法,嘿嘿);

4.dependencies中的vue默认安装2+,如果dependencies中的vue选择1.0.26,那么devDependencies中对应的vue-loader最好选择7.3.0,vue-hot-reload-api最好选择^1.2.0,否则就会报错;

5.dependencies中的vue-router默认安装2+,无法识别router.map()这个方法,如果想要用回这个方法,最好选择^0.7.13;

6.有时安装一个依赖项,会提示还需要一并安装别的依赖项,例如:如果要安装bootstrap-loader,会提示要求安装node-sass sass-loader resolve-url-loader;要安装less-loader,会提示要求安装less;

完成这一步之后,会在项目目录下生成一个名node_modules的文件,对应的package.json文件中的内容变动如下(我额外添加了几个依赖项):

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;"> "devDependencies": { "autoprefixer-loader": "^3.2.0", "babel-core": "^6.18.2", "babel-loader": "^6.2.7", "babel-plugin-transform-runtime": "^6.15.0", "babel-preset-es2015": "^6.18.0", "babel-preset-stage-0": "^6.16.0", "babel-runtime": "^6.18.0", "css-loader": "^0.25.0", "debug": "^2.2.0", "express": "^4.14.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.9.0", "html-webpack-plugin": "^2.24.1", "jquery": "^3.1.1", "less": "^2.7.1", "less-loader": "^2.2.3", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "vue-hot-reload-api": "^1.2.0", "vue-html-loader": "^1.2.3", "vue-loader": "^7.3.0", "webpack": "^1.13.3", "webpack-dev-middleware": "^1.8.4", "webpack-dev-server": "^1.16.2", "webpack-hot-middleware": "^2.13.1" }, "dependencies": { "vue": "^1.0.26", "vue-router": "^0.7.13" }</pre>

[
复制代码

](javascript:void(0); "复制代码")

三、在项目目录下新建一个名为src的目录,里面用于存放入口文件(index.js)、项目源文件(html,css,js,img之类的)、组件(.vue后缀),我的src目录结构大致如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">src -entry -index.js -pages -components -css -img -js -index.html -public</pre>

[
复制代码

](javascript:void(0); "复制代码")

当然,有输入目录,就有输出目录,即在项目目录下新建一个output目录,用于放置生产出来的各种资源文件。

四、在项目目录下新建一个名为build目录,里面用于存放各种配置文件,涉及到基础配置、开发和生产环境、静态服务器以及热加载,详细的内容请看下面的代码:

1.webpack.config.js(基础配置文件)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">// 引入依赖模块
var path = require('path'); var webpack = require('webpack'); var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = { // 入口文件,路径相对于本文件所在的位置,可以写成字符串、数组、对象
entry: { // path.resolve([from ...], to) 将to参数解析为绝对路径
index:path.resolve(__dirname, '../src/entry/index.js'), // 需要被提取为公共模块的群组
vendors:['vue','vue-router','jquery'],
}, // 输出配置
output: { // 输出文件,路径相对于本文件所在的位置
path: path.resolve(__dirname, '../output/static/js/'), // 设置publicPath这个属性会出现很多问题:
// 1.可以看成输出文件的另一种路径,差别路径是相对于生成的html文件;
// 2.也可以看成网站运行时的访问路径;
// 3.该属性的好处在于当你配置了图片CDN的地址,本地开发时引用本地的图片资源,上线打包时就将资源全部指向CDN了,如果没有确定的发布地址不建议配置该属性,特别是在打包图片时,路径很容易出现混乱,如果没有设置,则默认从站点根目录加载
// publicPath: '../static/js/',

    // 基于文件的md5生成Hash名称的script来防止缓存
    filename: '[name].[hash].js', // 非主入口的文件名,即未被列在entry中,却又需要被打包出来的文件命名配置
    chunkFilename: '[id].[chunkhash].js' }, // 其他解决方案

resolve: { // require时省略的扩展名,遇到.vue结尾的也要去加载
extensions: ['','.js', '.vue'], // 模块别名地址,方便后续直接引用别名,无须写长长的地址,注意如果后续不能识别该别名,需要先设置root
alias:{}
}, // 不进行打包的模块
externals:{}, // 模块加载器
module: { // loader相当于gulp里的task,用来处理在入口文件中require的和其他方式引用进来的文件,test是正则表达式,匹配要处理的文件;loader匹配要使用的loader,"-loader"可以省略;include把要处理的目录包括进来,exclude排除不处理的目录
loaders: [ // 使用vue-loader 加载 .vue 结尾的文件
{
test: /.vue/, loader: 'vue-loader', exclude: /node_modules/ }, // 使用babel 加载 .js 结尾的文件 { test: /\.js/,
loader: 'babel',
exclude: /node_modules/,
query:{
presets: ['es2015', 'stage-0'],
plugins: ['transform-runtime']
}
}, // 使用css-loader和style-loader 加载 .css 结尾的文件
{
test: /.css/, // 将样式抽取出来为独立的文件 loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader"), exclude: /node_modules/ }, // 使用less-loader、css-loader和style-loade 加载 .less 结尾的文件 { test: /\.less/, // 将样式抽取出来为独立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader"),
exclude: /node_modules/ }, // 加载图片
{
test: /.(png|jpg|gif)/, loader: 'url-loader', query: { // 把较小的图片转换成base64的字符串内嵌在生成的js文件里 limit: 10000, // 路径要与当前配置文件下的publicPath相结合 name:'../img/[name].[ext]?[hash:7]' } }, // 加载图标 { test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)/,
loader: 'file-loader',
query: { // 把较小的图标转换成base64的字符串内嵌在生成的js文件里
limit: 10000,
name:'../fonts/[name].[ext]?[hash:7]',
prefix:'font' }
},
]
}, // 配置插件项
plugins: []
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

2.webpack.dev.config.js(开发环境下的配置文件)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">// 引入依赖模块
var path = require('path'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 引入基本配置
var config = require('./webpack.config.js'); // 必须修改原配置中网站运行时的访问路径,相当于绝对路径,修改完之后,当前配置文件下的很多相对路径都是相对于这个来设定; // 注意:webpack-dev-server会实时的编译,但是最后的编译的文件并没有输出到目标文件夹,而是保存到了内存当中
config.output.publicPath = '/'; // 重新配置模块加载器
config.module= { // test是正则表达式,匹配要处理的文件;loader匹配要使用的loader,"-loader"可以省略;include把要处理的目录包括进来,exclude排除不处理的目录
loaders: [ // 使用vue-loader 加载 .vue 结尾的文件
{
test: /.vue/, loader: 'vue-loader', exclude: /node_modules/ }, // 使用babel 加载 .js 结尾的文件 { test: /\.js/,
loader: 'babel',
exclude: /node_modules/,
query:{
presets: ['es2015', 'stage-0'],
plugins: ['transform-runtime']
}
}, // 使用css-loader、autoprefixer-loader和style-loader 加载 .css 结尾的文件
{
test: /.css/, // 将样式抽取出来为独立的文件 loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader"), exclude: /node_modules/ }, // 使用less-loader、autoprefixer-loader、css-loader和style-loade 加载 .less 结尾的文件 { test: /\.less/, // 将样式抽取出来为独立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader"),
exclude: /node_modules/ }, // 加载图片
{
test: /.(png|jpg|gif)/, loader: 'url-loader', query: { // 把较小的图片转换成base64的字符串内嵌在生成的js文件里 limit: 10000, // 路径和生产环境下的不同,要与修改后的publickPath相结合 name: 'img/[name].[ext]?[hash:7]' } }, // 加载图标 { test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)/,
loader: 'file-loader',
query: {
limit: 10000, // 路径和生产环境下的不同,要与修改后的publickPath相结合
name:'fonts/[name].[ext]?[hash:7]',
prefix:'font' }
},
]
}; // 重新配置插件项
config.plugins = [ // 位于开发环境下
new webpack.DefinePlugin({ 'process.env': {
NODE_ENV: '"development"' }
}), // 自动生成html插件,如果创建多个HtmlWebpackPlugin的实例,就会生成多个页面
new HtmlWebpackPlugin({ // 生成html文件的名字,路径和生产环境下的不同,要与修改后的publickPath相结合,否则开启服务器后页面空白
filename: 'src/pages/index.html', // 源文件,路径相对于本文件所在的位置
template: path.resolve(__dirname, '../src/pages/index.html'), // 需要引入entry里面的哪几个入口,如果entry里有公共模块,记住一定要引入
chunks: ['vendors','index'], // 要把<script>标签插入到页面哪个标签里(body|true|head|false)
inject: 'body', // 生成html文件的标题
title:''
// hash如果为true,将添加hash到所有包含的脚本和css文件,对于解除cache很有用
// minify用于压缩html文件,其中的removeComments:true用于移除html中的注释,collapseWhitespace:true用于删除空白符与换行符
}), // 提取css单文件的名字,路径和生产环境下的不同,要与修改后的publickPath相结合
new ExtractTextPlugin("[name].[contenthash].css"), // 提取入口文件里面的公共模块
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
}), // 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
new webpack.optimize.OccurenceOrderPlugin(), // 模块热替换插件
new webpack.HotModuleReplacementPlugin(), // 允许错误不打断程序
new webpack.NoErrorsPlugin(), // 全局挂载插件
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery", "window.jQuery":"jquery" })
]; // vue里的css也要单独提取出来
config.vue = {
loaders: {
css: ExtractTextPlugin.extract("css")
}
}; // 启用source-map,开发环境下推荐使用cheap-module-eval-source-map
config.devtool='cheap-module-eval-source-map'; // 为了实现热加载,需要动态向入口配置中注入 webpack-hot-middleware/client ,路径相对于本文件所在的位置 // var devClient = 'webpack-hot-middleware/client'; // 为了修改html文件也能实现热加载,需要修改上面的devClient变量,引入同级目录下的dev-client.js文件
var devClient = './build/dev-client'; // Object.keys()返回对象的可枚举属性和方法的名称
Object.keys(config.entry).forEach(function (name, i) { var extras = [devClient];
config.entry[name] = extras.concat(config.entry[name]);
})

module.exports = config;</pre>

[
复制代码

](javascript:void(0); "复制代码")

3.webpack.prod.config.js(生产环境下的配置文件)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">// 引入依赖模块
var path = require('path'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 引入基本配置
var config = require('./webpack.config'); // 重新配置插件项
config.plugins = [ // 位于生产环境下
new webpack.DefinePlugin({ 'process.env': {
NODE_ENV: '"production"' }
}), // 自动生成html插件,如果创建多个HtmlWebpackPlugin的实例,就会生成多个页面
new HtmlWebpackPlugin({ // 生成html文件的名字,路径相对于输出文件所在的位置
filename: '../../html/index.html', // 源文件,路径相对于本文件所在的位置
template: path.resolve(__dirname, '../src/pages/index.html'), // 需要引入entry里面的哪几个入口,如果entry里有公共模块,记住一定要引入
chunks: ['vendors','special','index'], // 要把<script>标签插入到页面哪个标签里(body|true|head|false)
inject: 'body', // 生成html文件的标题
title:'', // hash如果为true,将添加hash到所有包含的脚本和css文件,对于解除cache很有用
// minify用于压缩html文件,其中的removeComments:true用于移除html中的注释,collapseWhitespace:true用于删除空白符与换行符
}), // 提取css单文件的名字,路径相对于输出文件所在的位置
new ExtractTextPlugin("../css/[name].[contenthash].css"), // 提取入口文件里面的公共模块
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
}), // 压缩js代码
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false }, // 排除关键字,不能混淆
except:['/pre>,'exports','require']
}), // 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
new webpack.optimize.OccurenceOrderPlugin(), // 全局挂载插件,当模块使用这些变量的时候,wepback会自动加载,区别于window挂载
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery", "window.jQuery":"jquery" })
]; // vue里的css也要单独提取出来
config.vue = {
loaders: {
css: ExtractTextPlugin.extract("css")
}
}; // 开启source-map,生产环境下推荐使用cheap-source-map或source-map,后者得到的.map文件体积比较大,但是能够完全还原以前的js代码
config.devtool='source-map'; // 关闭source-map // config.devtool=false;
module.exports = config;</pre>

[
复制代码

](javascript:void(0); "复制代码")

4.dev-server.js(服务器配置文件)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">// 引入依赖模块
var express = require('express'); var webpack = require('webpack'); var config = require('./webpack.dev.config.js'); // 创建一个express实例
var app = express(); // 对网站首页的访问返回 "Hello World!" 字样
app.get('/', function (req, res) {
res.send('Hello World!');
}); // 调用webpack并把配置传递过去
var compiler = webpack(config); // 使用 webpack-dev-middleware 中间件,搭建服务器
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true,
chunks: false }
}) // 使用 webpack-hot-middleware 中间件,实现热加载
var hotMiddleware = require('webpack-hot-middleware')(compiler); // 为了修改html文件也能实现热加载,使用webpack插件来监听html源文件改变事件
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { // 发布事件
hotMiddleware.publish({ action: 'reload' });
cb();
})
}); // 注册中间件
app.use(devMiddleware);
app.use(hotMiddleware); // 监听 8888 端口,开启服务器
app.listen(8888, function (err) { if (err) {
console.log(err); return;
}
console.log('Listening at http://localhost:8888');
})</pre>

[
复制代码

](javascript:void(0); "复制代码")

5.dev-client.js(配合dev-server.js监听html文件改动也能够触发自动刷新)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">// 引入 webpack-hot-middleware/client
var hotClient = require('webpack-hot-middleware/client'); // 订阅事件,当 event.action === 'reload' 时执行页面刷新
hotClient.subscribe(function (event) { if (event.action === 'reload') {
window.location.reload();
}
})</pre>

[
复制代码

](javascript:void(0); "复制代码")

五、为了不必每次构建项目都要输入webpack --display-modules --display-chunks --config build/webpack.config.js这条长命令,我们在package.js文件中修改“scripts”项:

<pre style="margin: 0px; padding: 0px; overflow: auto; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; white-space: pre-wrap;">"scripts": { "build":"webpack --display-modules --display-chunks --config build/webpack.config.js", "dev":"node ./build/dev-server.js" }</pre>

注意:package.js中不能有注释。

这样,我们就可以通过执行 npm run build 来进行构建,同时还增加了一条开启开发服务器的命令 npm run dev。

六、网上很多人讲解webpack配置是按“先……然后……”的逻辑往下走,以及每走一步会说明走这一步的原因是什么,配完之后的结果是什么,出了问题该怎么解决,这种撰文方式确实帮了很多入门webpack的小白们(譬如我)很大的忙。所以这里我就省略了这些步骤,而是直接将最后一步的配置结果展现出来给大家看,并且附上了详细的注释(写得呕心沥血啊)供大家理解,以后不出意外应该会出webpack构建vue的进阶篇,敬请期待

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352