[造轮子教程]十分钟搭建Webpack+Vue项目
varHarrie opened this issue · 11 comments
每一步的项目源码:这里
本教程涉及技术栈和相关工具为:webpack@1,vue@1,vue-router@0.7,vuex@1,eslint
对于webpack@2和vue@2,请参考官方文档作改动
如果项目本身并没有特别需求,还是推荐使用
vue-cli
构建项目,方便快捷
推荐vue项目目录结构:
- config 全局变量
- dist 编译后的项目代码
- src 项目源码
- apis api封装
- components Vue组件
- libs js工具类
- router 路由
- index.js 路由对象
- routes.js 路由配置
- store Vuex的store
- modules vuex模块
- types.js type管理
- styles css样式
- views 页面组件
- main.js vue入口文件
- webpack.config Webpack各种环境的配置文件
- package.json
第一步:初始化项目
-
所有项目的第一步当然是:创建项目文件夹,然后使用
npm init -y
创建package.json
-
项目根目录下建立
src
和dist
文件夹,分别用来存放项目源码
和webpack编译后的代码
第二步:入口文件
- 在
根目录
下直接建立一个index.html
,作为页面的入口文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<div id="app">{{message}}</div> <!-- Vue模板入口 -->
<script src="dist/main.js"></script>
</body>
</html>
- 在
src
下建立一个main.js
,作为Vue的入口文件
// import...from的语法是ES6的,需要用到babel,后面再说
// require的语法是Commonjs的,webpack已经实现了,可以直接使用
const Vue = require('vue')
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
- 安装模块
安装Vue:npm install vue@1 --save
安装Webpack: npm install webpack --save-dev
- 使用webpack编译打包
除非在全局安装webpack
,使用本地安装需要在package.json
的script
加入运行脚本,添加之后package.json
如下:
{
"name": "step2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack src/main.js dist/main.js" // <---添加这句
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^1.0.28"
},
"devDependencies": {
"webpack": "^1.14.0"
}
}
运行npm run dev
,再用浏览器打开index.html
就能看到效果了:
Hello Vue.js!
第三步:编写webpack配置文件
上一步中直接使用webpack运行脚本webpack [入口文件] [出口文件]
,显然对于后期添加webpack插件和不同环境的配置是不行的。
-
在项目根目录下创建
webpack.config
文件夹专门用于存放webpack的配置文件 -
为了让配置文件不同的编译环境中能够复用(例如
loaders
的配置,不管在开发环境还是生产环境肯定都是一样的),在webpack.confg
中首先创建一个base.js
文件:
const path = require('path')
const root = path.resolve(__dirname, '..') // 项目的根目录绝对路径
module.exports = {
entry: path.join(root, 'src/main.js'), // 入口文件路径
output: {
path: path.join(root, 'dist'), // 出口目录
filename: 'main.js' // 出口文件名
}
}
上面这段配置就实现了webpack src/main.js dist/main.js
的功能,还可以额外拓展一下,变成:
const path = require('path')
const root = path.resolve(__dirname, '..') // 项目的根目录绝对路径
module.exports = {
entry: path.join(root, 'src/main.js'), // 入口文件路径
output: {
path: path.join(root, 'dist'), // 出口目录
filename: 'main.js' // 出口文件名
},
resolve: {
alias: { // 配置目录别名
// 在任意目录下require('components/example') 相当于require('项目根目录/src/components/example')
components: path.join(root, 'src/components'),
views: path.join(root, 'src/views'),
styles: path.join(root, 'src/styles'),
store: path.join(root, 'src/store')
},
extensions: ['', '.js', '.vue'], // 引用js和vue文件可以省略后缀名
fallback: [path.join(root, 'node_modules')] // 找不到的模块会尝试在这个数组的目录里面再寻找
},
resolveLoader: {
fallback: [path.join(root, 'node_modules')] // 找不到的loader模块会尝试在这个数组的目录里面再寻找
},
module: { // 配置loader
loaders: [
{test: /\.vue$/, loader: 'vue'}, // 所有.vue结尾的文件,使用vue-loader
{test: /\.js$/, loader: 'babel', exclude: /node_modules/} // .js文件使用babel-loader,切记排除node_modules目录
]
}
}
根目录下添加.babelrc
用于配置babel
:
{
"presets": ["es2015"]
}
使用了vue-loader和babel-loader需要安装包:
npm install --save-dev vue-loader@8 babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015 css-loader vue-style-loader vue-hot-reload-api@1 vue-html-loader
- 在
webpack.confg
创建dev.js
文件:
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./base')
const root = path.resolve(__dirname, '..')
module.exports = merge(baseConfig, {})
上面的代码仅仅是导出了跟base.js
一模一样的配置,下面我们添加更多用于dev
(开发环境)的配置。
webpack-merge 用于合并两个配置文件,需要安装
npm install --save-dev webpack-merge
- 使用webpack dev server,开启一个小型服务器,不需要再手动打开
index.html
进行调试了
修改配置文件为:
module.exports = merge(baseConfig, {
devServer: {
historyApiFallback: true, // 404的页面会自动跳转到/页面
inline: true, // 文件改变自动刷新页面
progress: true, // 显示编译进度
colors: true, // 使用颜色输出
port: 3000, // 服务器端口
},
devtool: 'source-map' // 用于标记编译后的文件与编译前的文件对应位置,便于调试
})
- 添加热替换配置,每次改动文件不会再整个页面都刷新
安装webpack-dev-server
:npm install --save-dev webpack-dev-server
module.exports = merge(baseConfig, {
entry: [
'webpack/hot/dev-server', // 热替换处理入口文件
path.join(root, 'src/index.js')
],
devServer: { /* 同上 */},
plugins: [
new webpack.HotModuleReplacementPlugin() // 添加热替换插件
]
}
- 使用
HtmlWebpackPlugin
,实现js入口文件自动注入
module.exports = merge(baseConfig, {
entry: [ /* 同上 */ ],
devServer: { /* 同上 */ },
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: path.join(root, 'index.html'), // 模板文件
inject: 'body' // js的script注入到body底部
})
]
}
最后修改后完整的dev.js
请查看源码
这里的
HotModuleReplacementPlugin
是webpack
内置的插件,不需要安装但
HtmlWebpackPlugin
需要自行安装:npm install --save-dev html-webpack-plugin
在文件头中引入
const HtmlWebpackPlugin = require('html-webpack-plugin')
修改index.html
,去掉入口文件的引入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<div id="app">{{message}}</div> <!-- Vue模板入口 -->
<!-- 去掉js入口文件 -->
</body>
</html>
- 最后修改
package.json
中的webpack运行脚本为:
{
"dev": "webpack-dev-server --config webpack.config/dev.js"
}
为了测试webpack配置是否都生效了,下面创建一个vue组件src/components/Hello.vue
:
<template>
<div>{{message}}</div>
</template>
<script>
export default {
data: () => ({message: 'Hello Vue.js!'})
}
</script>
修改main.js
:
import Vue from 'vue'
import Hello from './components/Hello.vue'
new Vue({
el: '#app',
template: '<div><hello></hello></div>',
components: {Hello}
})
运行npm run dev
,浏览器打开localhost:3000
查看结果:
Hello Vue.js!
第四步:配置路由
-
安装
vue-router
:npm install --save vue-router@0.7
-
创建目录
在src
目录下创建views
文件夹,用于存放页面组件
在src
目录下创建router
文件夹,用于存放所有路由相关的配置
- 添加路由页面
添加页面组件src/views/Home.vue
:
<template>
<div><hello></hello></div>
</template>
<script>
import Hello from 'components/Hello'
export default {
components: {Hello}
}
</script>
添加src/router/routes.js
文件,用于配置项目路由:
import Home from 'views/Home'
export default {
'/': {
name: 'home',
component: Home
}
}
添加路由入口文件src/router/index.js
:
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({
hashbang: false, // 关闭hash模式
history: true, // 开启html5history模式
linkActiveClass: 'active' // v-link激活时添加的class,默认是`v-link-active`
})
router.map(routes)
router.beforeEach(({to, next}) => {
console.log('---------> ' + to.name) // 每次调整路由时打印,便于调试
next()
})
export default router
修改main.js
:
import Vue from 'vue'
import router from './router'
const App = Vue.extend({})
router.start(App, '#app')
最后别忘了编辑index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<div id="app">
<router-view></router-view><!--路由替换位置-->
</div>
</body>
</html>
重新执行npm run dev
,浏览器打开localhost:3000
查看效果
第五步:配置Vuex
vuex通常用于存放和管理不同组件中的共用状态,例如不同路由页面之间的公共数据
vuex中的几个概念:
state:状态,即数据
store:数据的集合,一个vuex引用,仅有一个store,包含n个state
mutation:state不能直接赋值,通过mutation定义最基本的操作
action:在action中调用一个或多个mutation
getter:state不能直接取值,使用getter返回需要的state
module:store和state之间的一层,便于大型项目管理,store包含多个module,module包含state、mutation和action
本教程中将以一个全局计数器作为例子
- 安装vuex
安装vuex
:npm install --save vuex@1
添加src/store
文件夹,存放vuex相关文件,添加src/store/modules
用于vuex分模块管理
- 添加
src/store/types.js
,vuex的所有mutation type
都放在一起,不建议分开多个文件,有效避免重名情况:
export const INCREASE = 'INCREASE' // 累加
export const RESET = 'RESET' // 清零
- 编写vuex模块,添加
counter
模块目录store/modules/counter
添加store/modules/counter/actions.js
:
import {INCREASE, RESET} from 'store/types'
export const increase = (({dispatch}) => {
dispatch(INCREASE) // 调用type为INCREASE的mutation
})
export const reset = (({dispatch}) => {
dispatch(RESET) // 调用type为RESET的mutation
})
添加store/modules/counter/index.js
import{INCREASE, RESET} from 'store/types.js'
const state = {
count: 0
}
const mutations = {
[INCREASE] (state) { state.count++ },
[RESET] (state) { state.count = 0 }
}
export default {state, mutations}
- 添加
store/index.js
,作为vuex入口文件
import Vue from 'vue'
import Vuex from 'vuex'
import counter from 'store/modules/counter'
Vue.use(Vuex) // 确保在new Vuex.Store()之前
export default new Vuex.Store({
modules: {counter}
})
- 修改
main.js
,将store引入并添加到App中:
import Vue from 'vue'
import router from './router'
import store from 'store'
const App = Vue.extend({store})
router.start(App, '#app')
- 最后改造一下
src/components/Hello.vue
,把action用上:
<template>
<div>
<p>{{message}}</p>
<p>click count: {{count}}</p>
<button @click="increase">increase</button><!--可以直接调用引入的action-->
<button @click="reset">reset</button>
</div>
</template>
<script>
import {increase, reset} from 'store/modules/counter/actions' // 引入action
export default {
data: () => ({message: 'Hello Vue.js!'}),
vuex: {
actions: {increase, reset},
getters: {
count: ({counter}) => counter.count
}
}
}
</script>
第六步:配置eslint
eslint不是必须的,但是强烈建议用在所有的javascript项目中
对于个人开发,可以在编程过程中发现并提示语法错误,有效过滤各种低级错误
对于团队开发,强制采用一致的编码风格,保证项目的统一性,有效避免各种任性行为
但是一定要注意,eslint定义的只是编码风格,规则是死的,人是活的,学会利用自定义规则的功能,增减规则
同时要知道,eslint检测不通过,不一定就是不能运行的,可能只是这种写法违背了编码风格,学会查看控制的查找具体错误原因
想要更好的eslint体验,请根据不同编辑器安装对应的eslint插件,主流的编辑器均已有相应插件
- 选择合适的编码风格
eslint提供了许多rules,可以直接在
.eslintrc
文件的rules
中一个一个的配置显然我们大多数情况下不需要这么做,网上已经有一些比较多人使用的风格了,本文推荐使用standard
- 配置
.eslintrc
文件
根目录下创建.eslintrc
文件:
{
"parser": "babel-eslint", // 支持babel
"extends": "standard", // 使用eslint-config-standard的配置
"plugins": [
"html" // 支持.vue文件的检测
],
"env": {
"browser": true, // 不会将window上的全局变量判断为未定义的变量
"es6": true // 支持es6的语法
},
"rules": { // 自定义个别规则写在这,0忽略,1警告,2报错
"no-unused-vars": 1 // 将”未使用的变量“调整为警告级别,原为错误级别,更多规则请看官网
}
}
结合不同编辑器的插件,打开js和vue文件中,就能看到提示了
根据使用的不同风格,安装所需的包,本文安装:
npm install --save-dev eslint babel-eslint eslint-config-standard eslint-plugin-standard eslint-plugin-html eslint-plugin-promise
第七步:webpack生产环境配置
前面已经配置过了开发环境下使用的配置文件dev.js
,对于生产环境,通常需要对编译出来的文件进行压缩处理,提取公共模块等等,就需要专门提供一个配置文件
- 添加
webpack.config/pro.js
文件,把生产环境用不到的删掉,比如webpack-dev-server
、webpack-hot-replacement
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const baseConfig = require('./base')
const root = path.resolve(__dirname, '..')
module.exports = merge(baseConfig, {
plugins: [
new HtmlWebpackPlugin({
template: path.join(root, 'index.html'), // 模板文件
inject: 'body' // js的script注入到body底部
})
]
})
webpack常用插件:
extract-text-webpack-plugin 提取css到单独的文件
compression-webpack-plugin 压缩gzip
webpack.optimize.UglifyJsPlugin 压缩js文件,内置插件
webpack.DefinePlugin 定义全局变量,内置插件
webpack.optimize.CommonsChunkPlugin 提取公共依赖,内置插件
根据项目需求添加相应的插件,插件配置参数请查看官方文档,这里不进行罗列
-
在
package.json
中添加运行脚本:"build": "webpack --config webpack.config/pro.js"
-
运行
npm run build
,可以在dist
文件夹中看到打包好的文件
完
“十分钟”只是噱头,多配几次,十分钟都不用。
轮子虽然都是圆的,也有轻重宽窄之分,造一辆车有时候也避免不了重造一个轮子。
666
在step 3 npm run dev 之后:
Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.resolve has an unknown property 'fallback'. These properties are valid:
object { alias?, aliasFields?, cachePredicate?, descriptionFiles?, enforceExtension?, enforceModuleExtension?, extensions?, fileSystem?, mainFields?, mainFiles?, moduleExtensions?, modules?, plugins?, resolver?, symlinks?, unsafeCache?, useSyncFileSystemCalls? } - configuration.resolve.extensions[0] should not be empty.
- configuration.resolveLoader has an unknown property 'fallback'. These properties are valid:
object { alias?, aliasFields?, cachePredicate?, descriptionFiles?, enforceExtension?, enforceModuleExtension?, extensions?, fileSystem?, mainFields?, mainFiles?, moduleExtensions?, modules?, plugins?, resolver?, symlinks?, unsafeCache?, useSyncFileSystemCalls? }
是因为webpack是1.0版本,需要升级的原因还是什么..?
@wendy-bear
应该是版本问题,webpack1和webpack2的配置项有改动,详细可以查看从v1迁移到v2
@wendy-bear
根据你的错误信息,你可以尝试:
- 将
resolve.fallback
和resolveLoader
删除 - 将
extensions: ['', '.js', '.vue']
改为extensions: ['.js', '.vue']
thx..
根据您的建议如此操作后,再接着出现类似的
configuration has an unknown property 'colors'.
configuration has an unknown property 'progress'.
这种无法解析的属性时都相应删掉后,该类报错得到解决~
再次运行出现新的问题:
ERROR in multi (webpack)-dev-server/client?http://localhost:3800 webpack/hot/dev-server ./src/main.js
Module not found: Error: Can't resolve 'babel' in '/Users/wenxia/webpackVue'
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.
You need to specify 'babel-loader' instead of 'babel',
see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
@ multi (webpack)-dev-server/client?http://localhost:3800 webpack/hot/dev-server ./src/main.js
You need to specify 'babel-loader' instead of 'babel',
相应去看了下是版本问题,现在不再支持缩写形式。在step 3 webpack.config/base.js
module: { // 配置loader
loaders: [
{ test: /\.vue$/, loader: 'vue-loader' }, // 所有.vue结尾的文件,使用vue-loader
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ } // .js文件使用babel-loader,切记排除node_modules目录
]
}
从此运行成功..
666~vue cli那个webpack模板虽然强大,但有些复杂,驾驭起来不足够顺手,还是自己造的“小轮子”得心应手。
为什么第二步没有结果?报错显示的是Vue is not a constructor
用 dawn ,三分钟搭建 react/vue 项目
npm i dawn -g
dn init -t vue
dn dev
好了,详细说明参考:https://github.com/alibaba/dawn/blob/master/README-zh.md
赞赞赞,顿悟, 中间有很多问题,主要是版本问题,慢慢都解决了。
第三步成功运行但是没有hello vue显示,控制台打印‘runtime only’,后来搜索了下找到这个地址:https://segmentfault.com/a/1190000006435886 ,修改webpack.config/dev.js 增加
resolve:{
alias: {
'vue': 'vue/dist/vue.js'
}
}
原因为
Vue 最早会打包生成三个文件,一个是 runtime only 的文件 vue.common.js,一个是 compiler only 的文件 compiler.js,一个是 runtime + compiler 的文件 vue.js。
第三步的 5 有问题。
之前没有创建 src/index.js
,这里却用到了,是不是应该是 src/main.js
module.exports = merge(baseConfig, {
entry: [
'webpack/hot/dev-server', // 热替换处理入口文件
path.join(root, 'src/index.js')
],
devServer: { /* 同上 */},
plugins: [
new webpack.HotModuleReplacementPlugin() // 添加热替换插件
]
}