├── .babelrc ├── .gitignore ├── README.md ├── _config.yml ├── dist ├── app.762ed07e.js ├── bg.urloader.jpg ├── bundle.js ├── index.html ├── main.f61208f8.js ├── runtime.12b274d9.js ├── style.fa126bd4.css └── vendor.95490bef.js ├── index.html ├── package.json ├── postcss.config.js ├── src ├── app.vue ├── assets │ ├── img │ │ └── bg.jpg │ └── style │ │ ├── footer.styl │ │ └── global.styl ├── index.js └── todo │ ├── footer.jsx │ ├── header.vue │ ├── items.vue │ ├── tabs.vue │ └── todo.vue └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ], 5 | "plugins":[ 6 | "transform-vue-jsx" 7 | ] 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-webpack-demo 2 | 3 | ## [webpack 4.0.0快速上手](https://github.com/chinadbo/webpack4-start) 4 | ## [A Vue.js Parcel-Bundler](https://github.com/chinadbo/vue-parcel) 5 | 6 | 7 | [前端工程化之自动构建gulp及模块打包webpack和parcel简介](https://github.com/chinadbo/web-front-end/issues/6) 8 | 9 | 10 | ## 目录 11 | 1. 通过webpack搭建vue工程workflow; 12 | 2. .vue文件开发模式; 13 | 3. vue使用jsx进行开发的方式; 14 | 4. vue组件间通信的基本方式; 15 | 5. webpack打包优化的基本点 16 | 17 | ## 通过webpack搭建vue工程 18 | 根目录webpack.config.js 19 | ```javascript 20 | const config = { 21 | entry: path.join(__dirname, 'src/index.js'), 22 | output: { 23 | filename: 'bundle.js', 24 | path: path.join(__dirname, 'dist'), 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.vue$/, 29 | loader: 'vue-loader' 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | ``` 36 | 查看更多[webpack.config.js](https://github.com/chinadbo/vue-webpack-demo/blob/master/webpack.config.js) 37 | 38 | ## .vue文件开发模式 39 | 在入口index.js文件中,声明Vue,并且挂在到$el元素上。 40 | ```javascript 41 | new Vue({ 42 | render: (h) => h(App) 43 | }).$mount($el) 44 | ``` 45 | 熟悉Vue之后可使用Vue官方推荐路由Vue-router 46 | ```javascript 47 | import Vue from 'vue' 48 | import App from './App' 49 | import router from './router' 50 | 51 | Vue.config.productionTip = false 52 | 53 | /* eslint-disable no-new */ 54 | new Vue({ 55 | el: '#app', 56 | router, 57 | template: '', 58 | components: { App } 59 | }) 60 | ``` 61 | 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。 62 | 63 | 所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。 64 | 65 | ```javascript 66 | import Header from './todo/header.vue' // 引入组件 67 | export default { 68 | components: { 69 | Header, 70 | } 71 | } 72 | ``` 73 | ```html 74 | 78 | ``` 79 | 80 | ### css预处理、后处理 81 | 开发过程中使用Sass、Less、Stylus等css预处理 82 | ```javascript 83 | // webpack.config.js 84 | { 85 | // 基本的css loader 86 | test: /\.css$/, 87 | use: [ 88 | 'style-loader', 89 | 'css-loader' 90 | ] 91 | }, 92 | { 93 | test: /\.styl$/, 94 | use: [ 95 | 'style-loader', 96 | 'css-loader', 97 | { 98 | loader: 'postcss-loader', 99 | options: { 100 | sourceMap: true 101 | } 102 | }, 103 | 'stylus-loader' 104 | ] 105 | } 106 | ``` 107 | 更进一步,加载postcss后处理器 108 | ```javascript 109 | // webpack.config.js 110 | { 111 | test: /\.styl$/, 112 | use: [ 113 | 'style-loader', 114 | 'css-loader', 115 | { 116 | loader: 'postcss-loader', 117 | options: { 118 | sourceMap: true 119 | } 120 | }, 121 | 'stylus-loader' 122 | ] 123 | } 124 | ``` 125 | 根目录新建postcss.config.js配置文件 126 | ```javascript 127 | const autoprefixer = require('autoprefixer') // 加载autoprefixer(浏览器兼容前缀) 128 | module.exports = { 129 | plugins: [ 130 | autoprefixer() 131 | ] 132 | } 133 | ``` 134 | 135 | ## JSX 136 | > React的核心机制之一就是可以在内存中创建虚拟的DOM元素。React利用虚拟DOM来减少对实际DOM的操作从而提升性能。 137 | 138 | JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。 139 | 140 | 如何在vue中使用jsx? 141 | ```javascript 142 | // webpack.config.js 143 | { 144 | test: /\.jsx$/, 145 | loader: 'babel-loader' 146 | } 147 | 148 | // 需要的package 149 | "babel-core": "^6.26.0", 150 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 151 | "babel-loader": "^7.1.2", 152 | "babel-plugin-syntax-jsx": "^6.18.0", 153 | "babel-plugin-transform-vue-jsx": "^3.5.0" 154 | 155 | // .babelrc 156 | { 157 | "presets": [ 158 | "env" 159 | ], 160 | "plugins":[ 161 | "transform-vue-jsx" 162 | ] 163 | } 164 | 165 | // jsx文件 166 | import '../assets/style/footer.styl' 167 | export default { 168 | data() { 169 | return { 170 | author: 'Ioodu' 171 | } 172 | }, 173 | render() { 174 | return ( 175 | 178 | ) 179 | } 180 | } 181 | ``` 182 | facebook.github.io [JSX语法](http://facebook.github.io/jsx/) 183 | 184 | ## vue组件间通信 185 | > 组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。 186 | 187 | 父组件 188 | ```vue 189 | 195 | 201 | ``` 202 | 父组件业务逻辑处理 203 | ```javascript 204 | // 引入组件 205 | components: { 206 | Item, 207 | Tabs, 208 | }, 209 | methods: { 210 | deleteTodo(id) { 211 | this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1) 212 | }, 213 | } 214 | ``` 215 | 子组件 216 | ```vue 217 | 218 | 219 | ``` 220 | ```javascript 221 | props: { 222 | todo: { 223 | type: Object, 224 | required: true 225 | } 226 | }, 227 | methods: { 228 | deleteTodo(){ 229 | this.$emit('del', this.todo.id) // 触发事件 并且带参数 230 | } 231 | } 232 | 233 | ``` 234 | ## webpack打包优化的基本点 235 | 236 | 1. 优化插件 237 | - OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID 238 | - UglifyJsPlugin:压缩JS代码; 239 | - ExtractTextPlugin:分离CSS和JS文件 240 | 2. 缓存 241 | 242 | - hash 243 | 244 | - clean-webpack-plugin 245 | 246 | ... 247 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /dist/app.762ed07e.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([0],[ 2 | /* 0 */ 3 | /***/ (function(module, exports) { 4 | 5 | /* 6 | MIT License http://www.opensource.org/licenses/mit-license.php 7 | Author Tobias Koppers @sokra 8 | */ 9 | // css base code, injected by the css-loader 10 | module.exports = function(useSourceMap) { 11 | var list = []; 12 | 13 | // return the list of modules as css string 14 | list.toString = function toString() { 15 | return this.map(function (item) { 16 | var content = cssWithMappingToString(item, useSourceMap); 17 | if(item[2]) { 18 | return "@media " + item[2] + "{" + content + "}"; 19 | } else { 20 | return content; 21 | } 22 | }).join(""); 23 | }; 24 | 25 | // import a list of modules into the list 26 | list.i = function(modules, mediaQuery) { 27 | if(typeof modules === "string") 28 | modules = [[null, modules, ""]]; 29 | var alreadyImportedModules = {}; 30 | for(var i = 0; i < this.length; i++) { 31 | var id = this[i][0]; 32 | if(typeof id === "number") 33 | alreadyImportedModules[id] = true; 34 | } 35 | for(i = 0; i < modules.length; i++) { 36 | var item = modules[i]; 37 | // skip already imported module 38 | // this implementation is not 100% perfect for weird media query combinations 39 | // when a module is imported multiple times with different media queries. 40 | // I hope this will never occur (Hey this way we have smaller bundles) 41 | if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { 42 | if(mediaQuery && !item[2]) { 43 | item[2] = mediaQuery; 44 | } else if(mediaQuery) { 45 | item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; 46 | } 47 | list.push(item); 48 | } 49 | } 50 | }; 51 | return list; 52 | }; 53 | 54 | function cssWithMappingToString(item, useSourceMap) { 55 | var content = item[1] || ''; 56 | var cssMapping = item[3]; 57 | if (!cssMapping) { 58 | return content; 59 | } 60 | 61 | if (useSourceMap && typeof btoa === 'function') { 62 | var sourceMapping = toComment(cssMapping); 63 | var sourceURLs = cssMapping.sources.map(function (source) { 64 | return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' 65 | }); 66 | 67 | return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); 68 | } 69 | 70 | return [content].join('\n'); 71 | } 72 | 73 | // Adapted from convert-source-map (MIT) 74 | function toComment(sourceMap) { 75 | // eslint-disable-next-line no-undef 76 | var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))); 77 | var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; 78 | 79 | return '/*# ' + data + ' */'; 80 | } 81 | 82 | 83 | /***/ }), 84 | /* 1 */ 85 | /***/ (function(module, exports, __webpack_require__) { 86 | 87 | /* 88 | MIT License http://www.opensource.org/licenses/mit-license.php 89 | Author Tobias Koppers @sokra 90 | Modified by Evan You @yyx990803 91 | */ 92 | 93 | var hasDocument = typeof document !== 'undefined' 94 | 95 | if (typeof DEBUG !== 'undefined' && DEBUG) { 96 | if (!hasDocument) { 97 | throw new Error( 98 | 'vue-style-loader cannot be used in a non-browser environment. ' + 99 | "Use { target: 'node' } in your Webpack config to indicate a server-rendering environment." 100 | ) } 101 | } 102 | 103 | var listToStyles = __webpack_require__(16) 104 | 105 | /* 106 | type StyleObject = { 107 | id: number; 108 | parts: Array 109 | } 110 | 111 | type StyleObjectPart = { 112 | css: string; 113 | media: string; 114 | sourceMap: ?string 115 | } 116 | */ 117 | 118 | var stylesInDom = {/* 119 | [id: number]: { 120 | id: number, 121 | refs: number, 122 | parts: Array<(obj?: StyleObjectPart) => void> 123 | } 124 | */} 125 | 126 | var head = hasDocument && (document.head || document.getElementsByTagName('head')[0]) 127 | var singletonElement = null 128 | var singletonCounter = 0 129 | var isProduction = false 130 | var noop = function () {} 131 | 132 | // Force single-tag solution on IE6-9, which has a hard limit on the # of 46 | -------------------------------------------------------------------------------- /src/assets/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinadbo/vue-webpack-start/506e48c37c479e27d48986aef8568207b1a8b3b5/src/assets/img/bg.jpg -------------------------------------------------------------------------------- /src/assets/style/footer.styl: -------------------------------------------------------------------------------- 1 | #footer 2 | text-align center 3 | margin-top 40px 4 | color #000 5 | font-size 10 6 | text-shadow 0 1px 0 #bfbfbf -------------------------------------------------------------------------------- /src/assets/style/global.styl: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | margin 0 4 | padding 0 5 | width 100% 6 | height 100% 7 | body 8 | background-image url('../img/bg.jpg') 9 | background-size cover 10 | background-position center center 11 | font-size 14px 12 | color #4d4d4d 13 | font-weight 300 14 | font-smoothing antialiased -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app.vue' 3 | 4 | import './assets/style/global.styl' 5 | 6 | const root = document.createElement('div') 7 | document.body.appendChild(root) 8 | 9 | new Vue({ 10 | render: (h) => h(App) 11 | }).$mount(root) -------------------------------------------------------------------------------- /src/todo/footer.jsx: -------------------------------------------------------------------------------- 1 | import '../assets/style/footer.styl' 2 | export default { 3 | data() { 4 | return { 5 | author: 'Ioodu' 6 | } 7 | }, 8 | render() { 9 | return ( 10 |
11 | Written by {this.author} 12 |
13 | ) 14 | } 15 | } -------------------------------------------------------------------------------- /src/todo/header.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /src/todo/items.vue: -------------------------------------------------------------------------------- 1 | 12 | 27 | 28 | 65 | -------------------------------------------------------------------------------- /src/todo/tabs.vue: -------------------------------------------------------------------------------- 1 | 15 | 47 | 48 | 84 | 85 | -------------------------------------------------------------------------------- /src/todo/todo.vue: -------------------------------------------------------------------------------- 1 | 24 | 70 | 71 | 91 | 92 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const isDev = process.env.NODE_ENV === 'development' 3 | const HTMLPlugin = require('html-webpack-plugin') 4 | const ExtractPlugin = require('extract-text-webpack-plugin') 5 | 6 | const webpack = require('webpack') 7 | const config = { 8 | target: 'web', 9 | entry: path.join(__dirname, 'src/index.js'), 10 | output: { 11 | filename: 'bundle.[hash:8].js', 12 | path: path.join(__dirname, 'dist') 13 | }, 14 | module: { 15 | rules: [ 16 | // { 17 | // test: /\.css$/, 18 | // use: [ 19 | // 'style-loader', 20 | // 'css-loader' 21 | // ] 22 | // }, 23 | { 24 | test: /\.vue$/, 25 | loader: 'vue-loader' 26 | }, 27 | { 28 | test: /\.jsx$/, 29 | loader: 'babel-loader' 30 | }, 31 | { 32 | test: /\.(gif|png|jpe?g|svg)$/, 33 | use: [ 34 | { 35 | loader: 'url-loader', 36 | options: { 37 | limit: 1024, 38 | name: '[name].urloader.[ext]' 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new webpack.DefinePlugin({ 47 | 'process.env': { 48 | NODE_ENV: isDev ? '"development"' : '"production"' 49 | } 50 | }), 51 | new HTMLPlugin() 52 | ] 53 | } 54 | 55 | if (isDev) { 56 | config.module.rules.push({ 57 | test: /\.styl$/, 58 | use: [ 59 | 'style-loader', 60 | 'css-loader', 61 | { 62 | loader: 'postcss-loader', 63 | options: { 64 | sourceMap: true 65 | } 66 | }, 67 | 'stylus-loader' 68 | ] 69 | }) 70 | config.devtool = '#cheap-module-eval-source-map' 71 | config.devServer = { 72 | port: 8000, 73 | host: '0.0.0.0', 74 | overlay: { 75 | errors: true 76 | }, 77 | hot: true, 78 | } 79 | config.plugins.push( 80 | new webpack.HotModuleReplacementPlugin(), 81 | new webpack.NoEmitOnErrorsPlugin() 82 | ) 83 | } else { 84 | config.entry = { 85 | app: path.join(__dirname, 'src/index.js'), 86 | vendor: ['vue'] 87 | } 88 | config.output.filename = '[name].[chunkhash:8].js' 89 | config.module.rules.push({ 90 | test: /\.styl$/, 91 | use: ExtractPlugin.extract({ 92 | fallback: 'style-loader', 93 | use: [ 94 | 'css-loader', 95 | { 96 | loader: 'postcss-loader', 97 | options: { 98 | sourceMap: true 99 | } 100 | }, 101 | 'stylus-loader' 102 | ] 103 | }) 104 | }) 105 | config.plugins.push( 106 | new ExtractPlugin('style.[contentHash:8].css'), 107 | new webpack.optimize.CommonsChunkPlugin({ 108 | name: 'vendor' 109 | }), 110 | new webpack.optimize.CommonsChunkPlugin({ 111 | name: 'runtime' 112 | }) 113 | ) 114 | } 115 | 116 | module.exports = config --------------------------------------------------------------------------------