使用webpack与vue2.0构建前端工程(一)
1.采用webpack打包文件,按需引入js和css2.采用vue2.0组件化开发方式3.根据项目需要,使用npm run dev和npm run build 可以分别运行dev脚本和build脚本进行不同的构建操作,dev脚本可以直接在本地建立一个服务器,直接查看我们的前端demo,实现前端代码的热部署,并且根据需要,可以将一些API请求转发到实际的后端服务器上,获取相应的json数据;buil
介绍
组件化开发和前后端分离是如今前端的发展趋势。
目前我在工作中采用的项目,基本上是jquery+bootstrap构建的,也没有采用任何的前端构建工具,碰到比较多的问题是经常需要进行dom操作,业务逻辑没办法和数据流解耦,导致后端接口更改或者前端页面有变动时,原来的代码逻辑都很难更改,所以这个系列的文章会讨论怎么把前端工程从原来的模式中解耦出来。
说到前端,最火的目前就是angular、react、vue三者了,考虑到vue的学习曲线相比前两个低(其实我是最先学的react),所以就采用了vue2.0我一直是一个不太喜欢写样式(css less)的人,因为我觉得自己对审美并不是有很高的天赋,而且自己也不喜欢为了一个样式扣半天。所以就考虑直接采用现成的css样式,组件的话复用一部分,自己写一部分。找了很多资料,采用了element饿了么的前端组件,这个组件由vue2.0构建,目前已经被应用于实际业务场景,详细文档可以访问http://element.eleme.io/#/zh-CN/
采用了组件化开发之后,随之而来的问题是,如何单独把前端项目从原来的开发模式中抽离出来,并且又可以在必要的时候整合到后端项目中,为了解决这个问题,我们用到的工具有
1.webpack 官网:http://webpackdoc.com/
2.node-glob github地址:https://github.com/isaacs/node-glob
3.webpack-plugin包括:html-webpack-plugin(打包html),extract-text-webpack-plugin(抽离CSS文件)
本文目标
1.采用webpack打包文件,按需引入js和css
2.采用vue2.0组件化开发方式
3.根据项目需要,使用npm run dev和npm run build 可以分别运行dev脚本和build脚本进行不同的构建操作,dev脚本可以直接在本地建立一个服务器,直接查看我们的前端demo,实现前端代码的热部署,并且根据需要,可以将一些API请求转发到实际的后端服务器上,获取相应的json数据;build脚本可以根据我们的后台项目需要把页面打包发布,然后把这个包丢给后端的同学们就可以了!
采用webpack打包
webpack是目前十分流行的一个打包工具,可以把任何静态资源按需引入。关于webpack,建议查看它的官网(上面有),这不是本文重点,只做简要介绍,如果你还对webpack不太熟悉,那么你最好先学习一下这个工具。另外推荐一下慕课网关于webpack的学习课程,http://www.imooc.com/learn/802,非常好的课程!
vue2.0
vue2.0这个也不做太多介绍了,官网https://cn.vuejs.org/文档十分齐全。
开始我们的工程
作为一个懒人,肯定不可能从npm init开始建立一个工程,所以我一开始就采用了项目模板
element快速上手模板:github.com/ElementUI/element-starter
主要是webpack配置项比较多,js css loader配置基本大同小异,我们姑且就在这个项目模板基础上更改 代码目录大概是这样的
饿了么快速上手模板工程
来看看它的webpack配置怎么写的
const {
resolve
} = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const url = require('url')
const publicPath = ''
module.exports = (options = {}) => ({
entry: {
vendor: './src/vendor',
index: './src/main.js'
},
output: {
path: resolve(__dirname, 'dist'),
filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
chunkFilename: '[id].js?[chunkhash]',
publicPath: options.dev ? '/assets/' : publicPath
},
module: {
rules: [{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
root: resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href']
}
}]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /favicon\.png$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}]
},
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
exclude: /favicon\.png$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000
}
}]
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
resolve: {
alias: {
'~': resolve(__dirname, 'src')
}
},
devServer: {
host: '127.0.0.1',
port: 8010,
proxy: {
'/api/': {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
historyApiFallback: {
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
}
},
devtool: options.dev ? '#eval-source-map' : '#source-map'
})
首先,webpack的config需要以commonjs规范返回一个config对象,这意味着我们可以在里面写任意的逻辑,在这里面它写成了通过函数(ES6箭头函数)返回对象的格式,好处是可以读取到命令行参数,这样子就可以根据产品和开发环境来进行不同的配置。(options.dev有值则为开发环境)
值得一提的是,这里这个项目模板已经为我们配置了devServer,项目中的package.json里面也已经加入了相关依赖,webpack-dev-server可以让我们实现热插拔,还可以设置代理进行URL转发,这也是我们前端脱离后端的基础。
这个配置文件已经为我们处理好了.vue,.css,.less,.sass和各种格式的图片文件(参见loader部分),并且采用了HtmlWebpackPlugin来打包html,同时它将公共的element组件部分抽出打包成vendor,利用了CommonsChunkPlugin来打包,避免对整个element组件的重复打包(commonsChunkPlugin中的manifest是指清单文件,本文不细讲)。
看起来好像已经挺完备的了,但是这个工程的打包入口只有一个,生成的html也只有一个,如果你需要打包多个html页面,那么意味着你需要自己在plugins中自己添加新的htmlWebpackPlugin配置和打包入口配置,而在一些大一点的工程项目中,我们的html页面往往有很多个,如果每次都要添加配置,就非常的繁琐了,有没有办法能让它自动写上配置呢?
参考了https://github.com/Plortinus/vue-multiple-pages这个项目里面的做法,我决定采用glob来读取项目中所有的.html文件,并且遵循一定的规则来自动匹配相应的入口文件,并把配置项写入到我们的config对象中。在这里我们默认约定大于配置,也就是我们的项目目录必须要遵循一定的约定,下面是我们项目的目录结构
├── dist # 打包后项目的文件地址
├── node_modules # 依赖
├── src # 项目代码
│ ├── assets # 静态资源文件
│ │ ├── css # css
│ │ ├── img # img
│ ├── common # 公共文件
│ │ ├── config.js # 公共配置文件
│ ├── pages # vue组件及入口文件 按模块划分
│ │ ├── college # college模块(假设有一个模块为college)
│ │ │ ├── index # index模块(假设有一个模块为college)
│ │ │ │ ├── App.vue # index 主vue组件
│ │ │ │ ├── index.js # index 主入口index.js
│ │ │ │ ├── index.html # index html模板 用于vue组件渲染
│ │ │ ├── login # login模块(假设有一个模块为college)
│ │ │ │ ├── App.vue # index 主vue组件
│ │ │ │ ├── login.js # index 主入口login.js
│ │ │ │ ├── login.html # login html模板 用于vue组件渲染
│ ├── components # vue组件 按三个层级划分 (公共组件|模块公共组件|页面组件)
│ │ ├── common # 公共组件
│ │ ├── index_common # 模块公共组件
├── webpack.config.js # webpack配置文件
├── package.json # 项目配置文件
├── postcss.config.js # postcss配置文件(编译CSS)
├── makefile
├── README.md
1.代码根目录主要由src dist两个文件夹,src即我们的前端工程代码,dist则是打包后的demo
2.assets存放静态资源文件
3.common存放一些前端的公共文件(例如配置文件可以保存开发环境和产品环境下的api接口地址)
4.pages则是我们的所有页面入口,里面按照模块分为两级,例如学院模块下可能有login index模块,每个子模块下会有一个主要的.vue,.js,.html文件,分别是主组件(绑定到元素),入口文件,和html模板。
5.components下存放所有的.vue组件,组件按照三个层级划分,即公共组件|模块公共组件|页面级组件,页面级组件可写在components下也可以直接写在模块文件夹中,直接按需引入。
有了以上这些约定,我们就可以让webpack来识别html文件和入口文件了。
首选处理打包入口:
//此处用于找到所有pages下的入口js,并写入webpack的entry配置中
glob.sync('./src/pages/**/*.js').forEach(path => {
const chunk = path.split('./src/pages/')[1].split('.js')[0]
if(chunk.split('/').length <=2){
// console.log(chunk);
}else{
nocommChunks.push(chunk);
}
entries[chunk] = path
chunks.push(chunk)
})
这段代码的意思是,遍历所有的pages下的以js结尾的文件,写入到我们的打包入口变量entries中,里面有一点trick是我们创建了一个一个非公共组件的chunks数组集合nocommChunks,这个在后面会用到。
然后用同样的方法处理html
//此处用于找到所有pages下的html模板,并采用htmlWebpackPlugin进行加载
//生产环境下 打包生成的html会放在 /dist/view/模块名 文件夹中
glob.sync('./src/pages/**/*.html').forEach(path => {
// console.log(path);
const allPath = path.split('./src/pages/')[1].split('.html')[0];
const modulename = path.split('./src/pages/')[1].split('.html')[0].split('/')[0];
const pagename = path.split('./src/pages/')[1].split('.html')[0].split('/')[1];
const filename = '/view/' + modulename + '/' + pagename + '.html'
// const chunk = path.split('./src/pages/')[1].split('.html')[0]
let tempChunks = nocommChunks.slice(0);
tempChunks.remove(allPath);
const htmlConf = {
filename: filename,
template: path,
excludeChunks:tempChunks
}
if(options.dev){
delete htmlConf.filename;
}
const newHtmlPlugin = new HtmlWebpackPlugin(htmlConf)
plugins.push(newHtmlPlugin)
});
这里面值得提的是,如果我们没有加上excludeChunks,那么打包后College/index/index.html和Collge/login/login.html都会把彼此的chunk也给引用上,也就是index.html中会有login.js打包后的代码块,login.html中会有index.js打包后的代码块,这显然不是我们想要的结果,所以我们这里把所有的非公共组件加到一个chunks中,再根据名字,把不属于自己的东西加入到excludeChunks,就避免了互相引用的问题。
最后,原来的项目模板里面没有把公共的CSS打包成一个模块,我们采用了ExtractTextPlugin模块来完成这个操作,详细可以看看我的注释
{
test: /\.css$/,
//这是一个坑,网上很多教程写的是ExtractTextPlugin.extract(["style-loader","css-loader"])
//实际上这种写法已经被抛弃,会导致webpack报错
//应采用以下这种写法
use: ExtractTextPlugin.extract({
fallback:"style-loader",
use : "css-loader"
}),
}
最后我们还希望打包后的目录能够直接与我们的后端项目相匹配,目前我的一个项目采用的是egg.js,它需要的打包后的目录如下:
├── dist # main
│ ├── public # 静态资源文件
│ │ ├── css # css
│ │ └── font # font
│ │ └── img # img
│ │ └── js # js
│ ├── components # components
│ └── view # html文件 按模块划分
│ │ ├──college
│ │ ├──college
│ │ ├──login
以上代码已经发布至github https://github.com/smalon/webpack-multiple-pages-vue2.0
更多推荐
所有评论(0)