├── .gitignore ├── LICENSE ├── README.md ├── img └── img.gif ├── index.html ├── package.json ├── server.js ├── src ├── index.js └── vue │ ├── compile │ ├── ast-to-render.js │ ├── diff.js │ ├── options.js │ ├── template-to-ast.js │ └── update.js │ ├── core │ ├── construction.js │ ├── dep.js │ ├── global.js │ ├── init-state.js │ ├── instance.js │ ├── patch.js │ ├── queue.js │ └── watcher.js │ ├── index.js │ ├── utils │ ├── define-reactive.js │ ├── index.js │ ├── merge-options.js │ ├── next-tick.js │ ├── normal.js │ └── proxy.js │ └── vnode │ └── index.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Editor directories and files 8 | .idea 9 | .vscode 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 看完源码还是自己手撸一个简单的加深功力 3 | 4 | 5 | 6 | ![截图](/img/img.gif) 7 | 8 | ## 已完成 9 | - [x] 勾选 初始化 10 | - [x] 数据劫持 11 | - [x] watcher observe dep等 12 | - [x] ast 13 | - [x] render函数 14 | - [x] vdom 15 | - [x] patch 16 | 第一版本完成 17 | 18 | ## 待完成(第二版本) 19 | - [ ] 代码优化,,现在代码结构有点乱 20 | - [ ] 支持组件 21 | 22 | 23 | 24 | *** 25 | 写完第一版感想: 26 | 27 | 其实写之前就知道最难的部分不是patch,不是数据劫持,而是字符串转token,再转ast,转render函数从而算出vdom。 28 | 29 | 1. 转token,真是考验正则功力!特别是对各种情况属性的匹配,对`<` 开头的各种处理等! 30 | 2. 转ast,稍微简单了点,只不过是吧杂乱的token整理规范化,注意的是以后我们本来整个render函数是当字符串运行,所以非表达式得 JSON.stringify下 31 | 3. 转render最头疼的就是调试问题,我们拼成render函数,在new Function运行,总会报错,各种缺少括号,乱七八糟 32 | 4. 转成功render生产vdom和patch就很简单了 33 | 34 | 所以说,到底基础得好到程度才能想出这么一套mvvm框架来,到底数据结构多牢固才能想出o(n)的diff算法来。我等菜鸡只能慢慢前进啊 -------------------------------------------------------------------------------- /img/img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingzhichen/mini-vue/b9150c6252b7bdb8b0a39806efd4561c204c1b34/img/img.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | vue 10 | 11 | 12 | 13 | 14 | 16 | 17 | 22 | 23 | 24 |
25 | 26 |

xx{{src}}

27 |

28 |
29 | {{list}}-{{idx}} 30 |
31 |
32 |

数组大于3显示我

33 |

小于3显示我

34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "0.0.1", 4 | "description": "", 5 | "license": "MIT", 6 | "keywords": [], 7 | "repository": "", 8 | "devDependencies": { 9 | "opn": "^5.1.0", 10 | "babel-core": "^6.26.0", 11 | "babel-loader": "^7.1.2", 12 | "babel-preset-env": "^1.6.0", 13 | "babel-preset-es2015": "^6.24.1", 14 | "clean-webpack-plugin": "^0.1.19", 15 | "copy-webpack-plugin": "^4.5.1", 16 | "css-loader": "^0.28.7", 17 | "cssnano": "^3.10.0", 18 | "cz-conventional-changelog": "^2.1.0", 19 | "express": "^4.16.3", 20 | "html-loader": "^0.5.1", 21 | "html-webpack-plugin": "^3.2.0", 22 | "mini-css-extract-plugin": "^0.4.0", 23 | "node-sass": "^4.5.3", 24 | "optimize-css-assets-webpack-plugin": "^4.0.0", 25 | "postcss-loader": "^2.1.5", 26 | "sass-loader": "^6.0.6", 27 | "uglifyjs-webpack-plugin": "^1.2.4", 28 | "url-loader": "^1.0.1", 29 | "webpack": "^4.5.0", 30 | "webpack-cli": "^2.0.14", 31 | "webpack-dev-middleware": "^3.1.3", 32 | "webpack-hot-middleware": "^2.22.3", 33 | "webpack-merge": "^4.1.1" 34 | }, 35 | "dependencies": {}, 36 | "scripts": { 37 | "test": "echo \"Error: no test specified\" && exit 1", 38 | "start": "node server.js", 39 | "build": "webpack --config webpack.prod.js" 40 | }, 41 | "config": { 42 | "commitizen": { 43 | "path": "./node_modules/cz-conventional-changelog" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const express = require("express") 3 | const webpack = require("webpack") 4 | const opn = require('opn') 5 | 6 | const webpackDevMiddleware = require("webpack-dev-middleware") 7 | const webpackHotMiddleware = require("webpack-Hot-middleware") 8 | const webpackConfig = require('./webpack.dev.js') 9 | 10 | const app = express(), 11 | DIST_DIR = path.join(__dirname, "dist"), // 设置静态访问文件路径 12 | PORT = 9090, // 设置启动端口 13 | complier = webpack(webpackConfig) 14 | 15 | 16 | let devMiddleware = webpackDevMiddleware(complier, { 17 | publicPath: webpackConfig.output.publicPath, 18 | noInfo: true, 19 | quiet: false, 20 | lazy: false, 21 | watchOptions: { 22 | poll: true 23 | }, 24 | stats: { 25 | colors: true 26 | }}) 27 | 28 | let hotMiddleware = webpackHotMiddleware(complier,{ 29 | log: false, 30 | heartbeat: 2000, 31 | }) 32 | app.use(devMiddleware) 33 | 34 | app.use(hotMiddleware); 35 | 36 | 37 | app.use(express.static(DIST_DIR)) 38 | app.listen(PORT,function(){ 39 | console.log("成功启动:localhost:"+ PORT) 40 | opn(`http://localhost:${PORT}`) 41 | }) -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './vue' 2 | window.vm = new Vue({ 3 | el: '#app', 4 | data: { 5 | src:'www.google.com', 6 | lists:['v','u','e'], 7 | inputValue:'input1', 8 | value:'input2', 9 | statur:true 10 | }, 11 | computed: { 12 | 13 | }, 14 | watch: { 15 | src(a,b){ 16 | console.log(`变化了${a}和${b}`) 17 | } 18 | 19 | }, 20 | methods: { 21 | click(){ 22 | console.log('click') 23 | } 24 | 25 | } 26 | }) 27 | if (module.hot) { 28 | module.hot.accept(); 29 | } -------------------------------------------------------------------------------- /src/vue/compile/ast-to-render.js: -------------------------------------------------------------------------------- 1 | import {addProp} from './template-to-ast' 2 | 3 | export function translateTorender(ast) { 4 | console.log(ast) 5 | let result = ast ? getRender(ast) : '_c("div")' 6 | result = `with(this){return ${result}}`; 7 | console.log(result) 8 | return new Function(result) 9 | 10 | } 11 | 12 | function getRender(ast) { 13 | let data = (getState(ast)) 14 | if (ast.for && !ast.hasFor) { 15 | return processFor(ast) 16 | } 17 | if (ast.if && !ast.hasIf) { 18 | return processIf(ast) 19 | } 20 | let children = `formatChildren(${genChildren(ast)})` 21 | return `_c(${JSON.stringify(ast.tagName)},${data ? data : {}},${children})` 22 | } 23 | 24 | 25 | function genChildren(ast) { 26 | let children = ast.children; 27 | if (children) { 28 | children = children.map(item => { 29 | if (item.type === 1) { 30 | return getRender(item) 31 | } else { 32 | return genText(item) 33 | } 34 | }).join(',') 35 | } 36 | return `[${children}]` 37 | 38 | } 39 | 40 | function processFor(ast) { 41 | let forInfo = ast.for 42 | // if (forInfo = ast.for && !ast.hasFor) { 43 | ast.hasFor = true 44 | return `_f(${forInfo.for},(function(${forInfo.alias},${forInfo.iterator1},${forInfo.iterator2}){return ${getRender(ast)}}))` 45 | // } 46 | 47 | } 48 | 49 | function processIf(ast) { 50 | ast.hasIf = true 51 | if (ast.ifJudge) { 52 | return genIf(ast.ifJudge.slice(0)) 53 | } 54 | 55 | 56 | } 57 | 58 | function genIf(conditions) { 59 | if (!conditions.length) { 60 | return 61 | } 62 | let condition = conditions.shift() || {}; 63 | if (condition.exp) { 64 | return `(${condition.exp })?${getRender(condition.block)}:${genIf(conditions)}` 65 | } else { 66 | return getRender(condition.block) 67 | } 68 | 69 | } 70 | 71 | function getState(element) { 72 | let data = '{' 73 | if (element.directives) { 74 | data += genDirectives(element, element.directives) + ',' 75 | } 76 | if (element.key) { 77 | data += `key:${element.key},` 78 | } 79 | if (element.attrs) { 80 | data += `attrs:{${getAttr(element.attrs)}},` 81 | } 82 | if (element.prop) { 83 | data += `domProps:{${getAttr(element.prop)}},` 84 | } 85 | if (element.events) { 86 | data += `on:${genHandler(element.events)}` 87 | } 88 | return data.replace(/,$/, '') + '}' 89 | 90 | } 91 | 92 | function genText(item) { 93 | return `_s(${ !item.expression ? JSON.stringify(item.text) : item.expression})` 94 | } 95 | 96 | function genDirectives(element, directives) { 97 | let res = 'directives:[' 98 | directives.forEach(item => { 99 | if (item.name === 'text') { 100 | addProp(element, 'textContent', `${item.value}`) 101 | } else if (item.name === 'html') { 102 | addProp(element, 'innerHTML', `_${item.value}`) 103 | } else if (item.name === 'model') { 104 | addProp(element, 'value', `(${item.value})`) 105 | addEvent(element, 'input', `(function($event){${item.value}=$event.target.value})`) 106 | 107 | } else { 108 | res += `{name:"${item.name}",value:${item.value}},` 109 | } 110 | }) 111 | return res.slice(0) + ']' 112 | } 113 | 114 | function genHandler(events) { 115 | let obj = '{' 116 | Object.keys(events).forEach(key => { 117 | obj = obj + `${ key}:` + genFunction(events[key]) + ',' 118 | }) 119 | return obj + '}' 120 | 121 | } 122 | 123 | function genFunction(handler) { 124 | return `function($event){${handler}.call(this,$event)}` 125 | } 126 | 127 | function getAttr(arr) { 128 | let res = ''; 129 | 130 | arr.forEach(item => { 131 | let key = Object.keys(item)[0] 132 | res += `"${key}":${item[key]},` 133 | }) 134 | return res 135 | } 136 | 137 | function addEvent(element, key, value) { 138 | (element.events || (element.events = {}))[key] = value; 139 | } 140 | -------------------------------------------------------------------------------- /src/vue/compile/diff.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingzhichen/mini-vue/b9150c6252b7bdb8b0a39806efd4561c204c1b34/src/vue/compile/diff.js -------------------------------------------------------------------------------- /src/vue/compile/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | isUnaryTag(tagName) { 3 | return 'area,br,hr,img,input'.split(',').filter(item => item === tagName).length > 0 4 | } 5 | } -------------------------------------------------------------------------------- /src/vue/compile/template-to-ast.js: -------------------------------------------------------------------------------- 1 | import options from './options' 2 | 3 | 4 | const startTagOpen = /^<([a-zA-Z_0-9\-]+)/; 5 | const startTagClose = /^\s*(\/?)>/; 6 | const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]*)))?/; 7 | const endTag = /^<\/([a-zA-Z_0-9\-]+)>/; 8 | const tagRE = /{{((.)+?)}}/g; 9 | const forRE = /([^]*?)\s+(?:in)\s+([^]*)/; 10 | const onRe = /^@|^v-on/; 11 | const bindRe = /^:|^v-bind:/; 12 | const vRe = /^v-|^:|^@/ 13 | 14 | export function translateToAst(template) { 15 | let currentParent, root; 16 | let stack = [] 17 | parseHTML(template, { 18 | isUnaryTag: options.isUnaryTag, 19 | start(tagName, attrs, unary, start, end) { 20 | 21 | let element = createASTElement(tagName, attrs, currentParent) 22 | if (!root) { 23 | root = element; 24 | } 25 | //解析 class 26 | //解析style 27 | processStaticNormal(element); 28 | //解析v-for 29 | processFor(element) 30 | //解析key 31 | processKey(element) 32 | 33 | //解析v-if 34 | processIf(element) 35 | //v-bind 36 | //v-on 37 | processAttrs(element) 38 | // v-text、v-html、v-show、、v-model这些在运行时处理 39 | //其他 40 | if (currentParent) { 41 | if (element.elseif || element.else) { 42 | const pre = currentParent.children[currentParent.children.length - 1] 43 | if (pre && pre.if) { 44 | pre.ifJudge.push({ 45 | exp: element.elseif, 46 | block: element 47 | }) 48 | } 49 | } else { 50 | currentParent.children.push(element) 51 | element.parent = currentParent 52 | 53 | } 54 | } 55 | if (!unary) { 56 | currentParent = element 57 | stack.push(element) 58 | } 59 | }, 60 | end(tagName, start, end) { 61 | stack.pop(); 62 | currentParent = stack[stack.length - 1]; 63 | }, 64 | chars(text) { 65 | text = text.trim(); 66 | if (text !== '') { 67 | let parseResult = parseText(text) 68 | if (parseResult) { 69 | currentParent.children.push({ 70 | expression: parseResult, 71 | type: 2, 72 | text: parseResult 73 | }) 74 | } else { 75 | currentParent.children.push({ 76 | type: 3, 77 | text 78 | }) 79 | } 80 | } 81 | } 82 | }) 83 | return root 84 | 85 | } 86 | 87 | 88 | function parseHTML(html, options) { 89 | let stack = [];//储存非一元标签 90 | let index = 0; 91 | let last, lastTag 92 | while (html) { 93 | last = html; 94 | let text; 95 | let leftArrow = html.indexOf('<'); 96 | if (leftArrow > 0) { //纯文本 97 | 98 | text = html.substring(0, leftArrow); 99 | advance(leftArrow) 100 | let lastMatch = stack[stack.length - 1] 101 | 102 | } 103 | if (leftArrow < 0) { 104 | text = html; 105 | html = '' 106 | } 107 | if (leftArrow === 0) { 108 | 109 | //省略处理注释 Doctype 110 | // End tag: 111 | const tagEnd = html.match(endTag) 112 | if (tagEnd) { 113 | const curIndex = index 114 | advance(tagEnd[0].length) 115 | options.end(tagEnd[1], curIndex, index) 116 | continue 117 | } 118 | 119 | //startTag 120 | const tagStart = html.match(startTagOpen); 121 | const match = { 122 | tagName: tagStart[1], 123 | attrs: [], 124 | start: index 125 | } 126 | advance(tagStart[0].length) 127 | let tagEndClose, attr 128 | while (!(tagEndClose = html.match(startTagClose)) && (attr = html.match(attribute))) { 129 | advance(attr[0].length) 130 | match.attrs.push(attr) 131 | } 132 | if (tagEndClose) { 133 | match.unarySlash = tagEndClose[1] 134 | advance(tagEndClose[0].length) 135 | match.end = index 136 | } 137 | 138 | const unary = options.isUnaryTag(match.tagName) || !!match.unarySlash 139 | match.attrs = match.attrs.map(item => { 140 | return { 141 | name: item[1], 142 | value: item[2] || item[3] || item[4] || true 143 | } 144 | }) 145 | if (!unary) { 146 | stack.push({tag: match.tagName, attrs: match.attrs}) 147 | lastTag = match.tagName 148 | } 149 | if (options.start) { 150 | options.start(match.tagName, match.attrs, unary, match.start, match.end) 151 | } 152 | 153 | } 154 | if (text) { 155 | options.chars(text) 156 | } 157 | } 158 | 159 | 160 | function advance(idx) { 161 | index += idx 162 | html = html.slice(idx); 163 | } 164 | 165 | 166 | } 167 | 168 | function createASTElement(tagName, attrs, parent) { 169 | return { 170 | type: 1, 171 | tagName, 172 | attrsList: attrs, 173 | attrsMap: makeAttrsMap(attrs), 174 | parent, 175 | children: [] 176 | } 177 | } 178 | 179 | function makeAttrsMap(attrs) { 180 | return attrs.reduce((obj, {name, value}) => { 181 | return { 182 | ...obj, 183 | [name]: value 184 | } 185 | }, {}) 186 | 187 | } 188 | 189 | function parseText(text) { 190 | let tokens = []; 191 | let match, index, last; 192 | while (match = tagRE.exec(text)) { 193 | last = match.index; 194 | tokens.push(JSON.stringify(text.slice(index, last))) 195 | let exp = match[1].trim(); 196 | tokens.push(`${exp}`) 197 | last = index = last + match[0].length 198 | } 199 | if (last < text.length) { 200 | tokens.push(text.slice(0, last)) 201 | } 202 | tokens = tokens.filter(item=>item.trim()!=='') 203 | return tokens.join('+') 204 | 205 | } 206 | 207 | function processStaticNormal(el) { 208 | ['class', 'style'].forEach(key => { 209 | const staticValue = getAndRemoveAttr(el, key); 210 | if (staticValue) { 211 | el[`static${key}`] = JSON.stringify(staticValue) 212 | } 213 | }) 214 | } 215 | 216 | function processFor(el) { 217 | let exp = getAndRemoveAttr(el, 'v-for'); 218 | if (exp) { 219 | const res = parseFor(exp) 220 | el.for = res; 221 | } 222 | } 223 | 224 | function parseFor(exp) { 225 | const match = exp.match(forRE) 226 | let result = {} 227 | result.for = match[2].trim(); 228 | let args = match[1] 229 | let a = args.indexOf('(') 230 | if (args.indexOf('(') >= 0) { 231 | args = args.replace('(', '') 232 | } 233 | if (args.indexOf(')') >= 0) { 234 | args = args.replace(')', '') 235 | } 236 | if (args.indexOf(',')) { 237 | args = args.split(',') 238 | result.alias = args[0] 239 | if (args[1]) { 240 | result.iterator1 = args[1] 241 | } 242 | if (args[2]) { 243 | result.iterator2 = args[2] 244 | 245 | } 246 | } else { 247 | result.alias = args 248 | } 249 | return result 250 | 251 | } 252 | 253 | function processKey(element) { 254 | let exp = getBindingAttr(element, 'key') 255 | if (exp) { 256 | element.key = exp 257 | } 258 | 259 | } 260 | 261 | function processIf(element) { 262 | let ifExp = getAndRemoveAttr(element, 'v-if') 263 | if (ifExp) { 264 | element.if = ifExp; 265 | element.ifJudge = [{ 266 | exp: ifExp, 267 | block: element 268 | }] 269 | } 270 | let elseIfExp = getAndRemoveAttr(element, 'v-else-if') 271 | if (elseIfExp) { 272 | element.elseIf = element 273 | } 274 | let elseExp = getAndRemoveAttr(element, 'v-else') 275 | if (elseExp) { 276 | element.else = true 277 | } 278 | } 279 | 280 | function processAttrs(element) { 281 | let attrLists = element.attrsList; 282 | attrLists.forEach(({name, value}) => { 283 | if (vRe.test(name)) { 284 | element.binding = true //不纯 285 | if (bindRe.test(name)) { 286 | name = name.replace(bindRe, ''); 287 | element.plain = false 288 | if (useProps(element.tagName, element.attrsMap.type, name)) { 289 | addProp(element, name, value) 290 | } else { 291 | addAttr(element, name, value) 292 | } 293 | } else if (onRe.test(name)) { 294 | element.plain = false 295 | name = name.replace(onRe, '') 296 | addHandler(element, name, value) 297 | } else { 298 | name = name.replace(vRe, '') 299 | addDirective(element, name, value) 300 | } 301 | } else { 302 | addAttr(element, name, JSON.stringify(value)) 303 | } 304 | }) 305 | 306 | } 307 | 308 | function getBindingAttr(el, name) { 309 | const dynamicValue = 310 | getAndRemoveAttr(el, ':' + name) || 311 | getAndRemoveAttr(el, 'v-bind:' + name) 312 | if (dynamicValue != null) { 313 | return dynamicValue 314 | } else { 315 | const staticValue = getAndRemoveAttr(el, name) 316 | if (staticValue != null) { 317 | return JSON.stringify(staticValue) 318 | } 319 | } 320 | } 321 | 322 | function getAndRemoveAttr(el, name, removeFromMap) { 323 | let val 324 | if ((val = el.attrsMap[name]) != null) { 325 | const list = el.attrsList 326 | for (let i = 0, l = list.length; i < l; i++) { 327 | if (list[i].name === name) { 328 | list.splice(i, 1) 329 | break 330 | } 331 | } 332 | } 333 | if (removeFromMap) { 334 | delete el.attrsMap[name] 335 | } 336 | return val 337 | } 338 | 339 | function useProps(tag, type, name) { 340 | return (tag === 'input' || tag === 'textarea') && (name === 'value') 341 | } 342 | 343 | export function addProp(element, name, value) { 344 | (element.props || (element.prop = [])).push({ 345 | [name]: value 346 | }) 347 | } 348 | 349 | export function addAttr(element, name, value) { 350 | (element.attrs || (element.attrs = [])).push({ 351 | [name]: value 352 | }) 353 | } 354 | 355 | function addHandler(element, name, value) { 356 | let events = element.events || (element.events = {}) 357 | if (!events[name]) { 358 | events[name] = [] 359 | } 360 | events[name].push(value) 361 | } 362 | 363 | function addDirective(element, name, value) { 364 | let directives = element.directives || (element.directives = []) 365 | directives.push({ 366 | name, 367 | value 368 | }) 369 | } 370 | -------------------------------------------------------------------------------- /src/vue/compile/update.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingzhichen/mini-vue/b9150c6252b7bdb8b0a39806efd4561c204c1b34/src/vue/compile/update.js -------------------------------------------------------------------------------- /src/vue/core/construction.js: -------------------------------------------------------------------------------- 1 | import {mergeOptions} from '../utils' 2 | import initState from './init-state' 3 | let uid = 0; 4 | export default function (Vue) { 5 | Vue.prototype.init = function (options) { 6 | let vm = this; 7 | vm._uid = ++uid; 8 | vm._isVue = true; 9 | vm.$options = mergeOptions( 10 | vm.constructor, 11 | options, 12 | vm 13 | ); 14 | vm._self = vm; 15 | //处理methods compited,data,watch 16 | //依赖收集 17 | initState(vm) 18 | //mounted 19 | if (vm.$options.el) { 20 | vm.$mount(vm.$options.el) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/vue/core/dep.js: -------------------------------------------------------------------------------- 1 | let depId = 0 2 | 3 | export default class Dep { 4 | constructor() { 5 | this.depId = ++depId; 6 | this.watchers = [] 7 | } 8 | 9 | depend() { 10 | const watcher = Dep.target; 11 | const isDepend = !(this.watchers.filter(item => item.watcherId === watcher.watcherId).length > 0); 12 | if (isDepend) { 13 | this.watchers.push(watcher); 14 | watcher.appendDep(this); 15 | } 16 | } 17 | 18 | notify() { 19 | this.watchers.forEach(item => item.update()); 20 | } 21 | 22 | } 23 | Dep.setTarget = function (watcher) { 24 | Dep.target = watcher 25 | } 26 | window.Dep = Dep 27 | -------------------------------------------------------------------------------- /src/vue/core/global.js: -------------------------------------------------------------------------------- 1 | import {nextTick} from '../utils/index' 2 | export default function (Vue) { 3 | Vue.nextTick = nextTick 4 | Vue.options = Object.create(null) 5 | Vue.options.base = Vue 6 | } -------------------------------------------------------------------------------- /src/vue/core/init-state.js: -------------------------------------------------------------------------------- 1 | import {isArray, defineReactive, proxy} from '../utils' 2 | import {isObject} from '../utils' 3 | import Watcher from './watcher' 4 | import Dep from './dep' 5 | export default function (vm) { 6 | vm._watchers = []; 7 | const options = vm.$options; 8 | if (options.props) initProps(vm, options.props) 9 | if (options.methods) initMethods(vm, options.methods) 10 | if (options.data) { 11 | initData(vm) 12 | } 13 | if (options.computed) initComputed(vm, options.computed) 14 | if (options.watch) { 15 | initWatch(vm, options.watch) 16 | } 17 | } 18 | 19 | function initProps() { 20 | 21 | } 22 | 23 | function initMethods(vm, methods) { 24 | proxy(methods, vm); 25 | } 26 | 27 | export function initData(vm) { 28 | let data = vm.$options.data; 29 | proxy(data, vm); 30 | observe(data) 31 | 32 | } 33 | 34 | export function initComputed(vm, computes) { 35 | //绑定到this 36 | proxy(computes, vm); 37 | //增加依赖 38 | Object.keys(computes).forEach(key => { 39 | const computed = computes[key]; 40 | Object.defineProperty(computes, key, { 41 | get() { 42 | let watcher = new Watcher(computed, vm,true) 43 | return watcher.getValue() 44 | }, 45 | }) 46 | 47 | }) 48 | 49 | } 50 | 51 | export function initWatch(vm,watchs) { 52 | Object.keys(watchs).forEach(watch=>{ 53 | new Watcher(watchs[watch],vm,false,watch) 54 | }) 55 | } 56 | 57 | 58 | export function observe(data) { 59 | let ob 60 | if (data.hasOwnProperty('_ob_)')) { 61 | ob = data._ob_ 62 | } else if (isObject(data)) { 63 | ob = new Observer(data) 64 | } else { 65 | ob = null 66 | } 67 | return ob 68 | 69 | } 70 | 71 | class Observer { 72 | constructor(data) { 73 | this.value = data; 74 | this.dep = new Dep(); 75 | this.vmCount = 0; 76 | data._ob_ = this; 77 | if (isArray(data)) { 78 | this.observeArray(data) 79 | } else { 80 | Object.keys(data).forEach(item => { 81 | defineReactive(data, item) 82 | 83 | }) 84 | } 85 | } 86 | 87 | 88 | observeArray(data) { 89 | data.forEach(item => { 90 | if (isObject(item)) { 91 | observe(item) 92 | } 93 | }) 94 | } 95 | } -------------------------------------------------------------------------------- /src/vue/core/instance.js: -------------------------------------------------------------------------------- 1 | import {nextTick} from '../utils' 2 | import {translateToAst} from '../compile/template-to-ast' 3 | import {translateTorender} from '../compile/ast-to-render' 4 | import Watcher from "./watcher"; 5 | import patch from './patch' 6 | import {createTextNode, createHtmlNode, createNormalNode} from '../vnode' 7 | 8 | export default function (Vue) { 9 | Vue.prototype.$nextTick = nextTick 10 | Vue.prototype._update = function (oldVnode, newVnode, vm) { 11 | patch(oldVnode, newVnode, vm) 12 | } 13 | Vue.prototype.$mount = function (el) { 14 | const vm = this; 15 | const ele = document.querySelector(el).outerHTML.trim(); 16 | const ast = translateToAst(ele); 17 | const render = translateTorender(ast); 18 | const _render = function () { 19 | return render.call(vm) 20 | } 21 | vm.$options._render = _render; 22 | const fn = function () { 23 | let vnode = vm.$options._render(); 24 | console.log(vnode) 25 | vm._update(vm.vnode, vnode, vm); 26 | vm.vnode = vnode 27 | } 28 | new Watcher(fn, this, false) 29 | } 30 | 31 | // for 32 | Vue.prototype._f = function (lists, children) { 33 | return Object.keys(lists).map(key => { 34 | if (!/^_/.test(key)) { 35 | return children(lists[key], key) 36 | } 37 | }).filter(_ => _) 38 | } 39 | 40 | 41 | //createElement 42 | Vue.prototype._c = function (tag, data, children) { 43 | return createNormalNode(tag, data, children) 44 | 45 | } 46 | //createTextNode 47 | Vue.prototype._s = function (text) { 48 | return createTextNode(text) 49 | 50 | } 51 | //html 52 | Vue.prototype._h = function (html) { 53 | return createHtmlNode(html) 54 | } 55 | Vue.prototype.formatChildren = function (arr) { 56 | let result = [] 57 | arr.forEach(item => { 58 | if (item instanceof Array) { 59 | result = result.concat(this.formatChildren(item)) 60 | } else { 61 | result.push(item) 62 | } 63 | }) 64 | return result 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/vue/core/patch.js: -------------------------------------------------------------------------------- 1 | let domPropsHandlerMap = { 2 | 'value': function (ele, key, value) { 3 | ele.value = value || '' 4 | }, 5 | 'textContent': function (ele, key, value) { 6 | ele.innerText = value || ''; 7 | return true 8 | }, 9 | 'innerHTML': function (ele, key, value) { 10 | ele.innerHTML = value || ''; 11 | return true 12 | } 13 | } 14 | 15 | let traverseObj = obj => callback => { 16 | Object.keys(obj).forEach(key => { 17 | let value = obj[key] 18 | if (callback(obj, key, value)) { 19 | return 20 | } 21 | }) 22 | } 23 | 24 | 25 | function isRealynode(ele) { 26 | return ele instanceof Node 27 | } 28 | 29 | function isExist(arg) { 30 | return arg != null 31 | } 32 | 33 | function $(arg) { 34 | return document.querySelector(arg) 35 | } 36 | 37 | function sameNode(oldVnode, newVnode) { 38 | if (!isExist(oldVnode.tag) && oldVnode.text) { 39 | return true 40 | } 41 | return oldVnode.tag === newVnode.tag && 42 | oldVnode.key === newVnode.key && 43 | isExist(oldVnode.data) && isExist(newVnode.data) 44 | } 45 | 46 | let parent = null 47 | export default function patch(oldVnode, newVnode, vm) { 48 | //当oldVnode不存在时候 渲染整个页面 49 | if (!isExist(oldVnode)) { 50 | if (isExist(newVnode)) { 51 | // renderPage(newVnode, , vm) 52 | insertElement($(vm.$options.el), newVnode, null, vm) 53 | return 54 | } 55 | } 56 | if (!isExist(newVnode)) { 57 | removeDom(oldVnode) 58 | return 59 | } 60 | diff(oldVnode, newVnode, vm) 61 | } 62 | 63 | 64 | function diff(oldVnode, newVnode, vm) { 65 | newVnode.parent = oldVnode.parent 66 | newVnode.dom = oldVnode.dom 67 | if (oldVnode.text) { 68 | if (newVnode.children) { 69 | insertElement(oldVnode.parent.dom, newVnode, null, vm) 70 | } 71 | 72 | } 73 | if (newVnode.text) { 74 | if (oldVnode.children) { 75 | insertElement(oldVnode.parent.dom, newVnode, null, vm) 76 | return 77 | } 78 | if (oldVnode.text) { 79 | if (oldVnode.text !== newVnode.text) { 80 | oldVnode.dom.nodeValue = newVnode.text; 81 | return 82 | } 83 | } 84 | return 85 | } 86 | if (sameNode(oldVnode, newVnode)) { 87 | updateAttrs(oldVnode, newVnode, vm) //属性比较 88 | updateDomProps(oldVnode, newVnode, vm) 89 | if (oldVnode.children && newVnode.children) { 90 | if (!oldVnode.children.length && newVnode.children.length) { 91 | insertElement(oldVnode.parent.dom, newVnode, null, vm) 92 | } 93 | if (oldVnode.children.length && !newVnode.children.length) { 94 | removeDom(oldVnode.children) 95 | } 96 | if (oldVnode.children.length && newVnode.children.length) { 97 | updateChildren(oldVnode.children, newVnode.children, vm) 98 | } 99 | } 100 | 101 | } else { 102 | insertElement(oldVnode.parent.dom, newVnode, null, vm) 103 | } 104 | 105 | 106 | } 107 | 108 | 109 | function updateChildren(oldChildren, newChildren, vm) { 110 | let oldStart = 0, 111 | oldEnd = oldChildren.length - 1, 112 | newStart = 0, 113 | newEnd = newChildren.length - 1; 114 | while (oldStart <= oldEnd && newStart <= newEnd) { 115 | if (sameNode(oldChildren[oldStart], newChildren[newStart])) { 116 | diff(oldChildren[oldStart], newChildren[newStart], vm); 117 | oldStart++; 118 | newStart++; 119 | continue 120 | } 121 | if (sameNode(oldChildren[oldEnd], newChildren[newEnd])) { 122 | diff(oldChildren[oldEnd], newChildren[newEnd], vm); 123 | oldEnd--; 124 | newEnd--; 125 | continue 126 | } 127 | if (sameNode(oldChildren[oldStart], newChildren[newEnd])) { 128 | diff(oldChildren[oldStart], newChildren[newEnd], vm); 129 | insertElement( 130 | oldChildren[oldStart].parent.dom, 131 | newChildren[newEnd].dom, 132 | oldChildren[oldEnd].nextSibling, 133 | vm 134 | ) 135 | oldStart++; 136 | newEnd--; 137 | continue; 138 | } 139 | if (sameNode(oldChildren[oldEnd], newChildren[newStart])) { 140 | diff(oldChildren[oldEnd], newChildren[newStart], vm); 141 | insertElement( 142 | oldChildren[oldStart].parent.dom, 143 | newChildren[newStart].dom, 144 | oldStart[oldEnd].dom, 145 | vm 146 | ) 147 | oldStart--; 148 | newEnd++; 149 | continue 150 | } 151 | if (!isExist(oldChildren[oldStart])) { 152 | oldStart++; 153 | continue; 154 | } 155 | if (!isExist(oldChildren[oldEnd])) { 156 | oldEnd--; 157 | continue; 158 | } 159 | //比较有可能存在相同k值 160 | let keyObj = {} 161 | oldChildren.forEach((item, idx) => { 162 | keyObj.key && (keyObj[keyObj.key] = idx) 163 | }) 164 | let index; 165 | if (index = keyObj[newChildren[newStart].key]) { 166 | diff(oldChildren[index], newChildren[newStart]) 167 | insertElement( 168 | oldChildren[index].parent.dom, 169 | newChildren[newStart].dom, 170 | oldChildren[oldStart].dom, 171 | vm 172 | ) 173 | oldChildren[index] = null; 174 | } else { 175 | debugger 176 | insertElement( 177 | oldChildren[oldStart].parent.dom, 178 | newChildren[newStart], 179 | oldChildren[oldStart].dom, 180 | vm 181 | ) 182 | } 183 | 184 | newStart++; 185 | 186 | } 187 | if (oldStart <= oldEnd) { 188 | 189 | while (oldStart <= oldEnd) { 190 | removeDom(oldChildren[oldStart]) 191 | oldStart++ 192 | } 193 | 194 | } 195 | if (newStart <= newEnd) { 196 | while (newStart <= newEnd) { 197 | insertElement( 198 | oldChildren[oldEnd].dom, 199 | newChildren[newStart], 200 | oldChildren[oldEnd].dom.nextSibling, 201 | vm, 202 | true 203 | ) 204 | newStart++ 205 | } 206 | } 207 | 208 | } 209 | 210 | function updateDomProps(oldVnode, newVnode, vm) { 211 | let oldProps = (oldVnode && oldVnode.data && oldVnode.data.domProps) || {}, 212 | newProps = (newVnode && newVnode.data && newVnode.data.domProps) || {}; 213 | traverseObj(newProps)((obj, key, value) => { 214 | if (oldProps[key] !== value) { 215 | domPropsHandlerMap[key](oldVnode.dom, key, value) 216 | } 217 | }) 218 | } 219 | 220 | function insertElement(parent, Vnode, beforeDom, vm, mustInsert) { 221 | debugger 222 | let dom = Vnode instanceof Node ? vnode : createElement(Vnode, vm) 223 | let _parent = parent.parentNode 224 | if (mustInsert || beforeDom) { 225 | parent.insertBefore(dom, beforeDom) 226 | } 227 | else { 228 | _parent.replaceChild(dom, parent) 229 | } 230 | 231 | } 232 | 233 | function removeDom(vnode) { 234 | if (vnode instanceof Array) { 235 | vnode.forEach(item => { 236 | item.dom && item.dom.parentNode.removeChild(item.dom); 237 | }) 238 | } else { 239 | vnode.dom && vnode.dom.parentNode.removeChild(vnode.dom); 240 | } 241 | 242 | } 243 | 244 | function createElement(vnode, vm) { 245 | let ele; 246 | if (vnode.tag) { 247 | ele = document.createElement(vnode.tag); 248 | } else { 249 | ele = document.createTextNode(vnode.text); 250 | } 251 | let isReturn = setDomprops(vnode, ele) 252 | vnode.dom = ele; 253 | addAttrs(vnode) 254 | if (isReturn) return ele 255 | setEvent(vnode, ele, vm) 256 | if (vnode.text) { 257 | ele.innerText = vnode.text 258 | } 259 | if (vnode.html) { 260 | ele.innerHTML = vnode.html; 261 | } 262 | if (vnode.children && vnode.tag) { 263 | vnode.children.forEach(item => { 264 | item.parent = vnode 265 | ele.appendChild(createElement(item, vm)) 266 | }) 267 | } 268 | return ele 269 | 270 | } 271 | 272 | 273 | function setEvent(vnode, ele, vm) { 274 | let data = vnode.data || {}; 275 | if (data.on) { 276 | Object.keys(data.on).forEach(key => { 277 | ele.addEventListener(key, (data.on[key]).bind(vm), false) 278 | }) 279 | } 280 | } 281 | 282 | function updateAttrs(oldVnode, newVnode, vm) { 283 | let oldData = oldVnode.data || {}; 284 | let newData = oldVnode.data || {}; 285 | 286 | if (isExist(oldData.attrs) && !isExist(newData.attrs)) { 287 | removeAttrs(oldVnode) 288 | return 289 | } 290 | if (!isExist(oldData.attrs) && isExist(newData.attrs)) { 291 | addAttrs(oldVnode) 292 | return 293 | } 294 | if (isExist(oldData.attrs) && isExist(newData.attrs)) { 295 | Object.keys(newData.attrs).forEach(key => { 296 | if (oldData.attrs.key) { 297 | if (oldData.attrs.key !== newData.attrs[key]) { 298 | addAttrs(oldVnode, key, newData.attrs[key]) 299 | } 300 | } 301 | }) 302 | Object.keys(newData.attrs).forEach(key => { 303 | if (!newData.attrs.key) { 304 | removeAttrs(oldVnode, key) 305 | } 306 | }) 307 | } 308 | } 309 | 310 | function addAttrs(vnode, key, value) { 311 | if (!key && !value) { 312 | if (vnode.data && vnode.data.attrs) { 313 | traverseObj(vnode.data.attrs)((obj, key, value) => vnode.dom.setAttribute(key, value)) 314 | 315 | } 316 | } else { 317 | vnode.dom.setAttribute(key, value) 318 | } 319 | 320 | } 321 | 322 | function setDomprops(vnode, ele) { 323 | let data = vnode.data || {}; 324 | if (data.domProps) { 325 | traverseObj(data.domProps)(function (obj, key, value) { 326 | domPropsHandlerMap[key](ele, key, value) 327 | }) 328 | } 329 | } 330 | 331 | function removeAttrs(vnode, key) { 332 | if (!key) { 333 | if (vnode.data && vnode.data.attrs) { 334 | traverseObj(vnode.data.attrs)((obj, key, value) => vnode.dom.removeAttribute(key)) 335 | } 336 | } else { 337 | vnode.dom.removeAttribute(key) 338 | } 339 | } 340 | 341 | 342 | -------------------------------------------------------------------------------- /src/vue/core/queue.js: -------------------------------------------------------------------------------- 1 | import {nextTick} from '../utils' 2 | 3 | let isFlushing = false; 4 | let queue = [] 5 | export default function (watcher) { 6 | 7 | if (!isFlushing) { 8 | queue.push(watcher) 9 | } else { 10 | let i = queue.length - 1 11 | while (i > 0 && queue[i].watcherId > watcher.watcherId) { 12 | i-- 13 | } 14 | queue.splice(i + 1, 0, watcher) 15 | } 16 | nextTick(flush) 17 | } 18 | 19 | function flush() { 20 | isFlushing = true; 21 | queue.sort((a, b) => a.watcherId - b.watcherId); 22 | queue.forEach(item => { 23 | item.getValue() 24 | }) 25 | isFlushing = false; 26 | queue = []; 27 | 28 | } -------------------------------------------------------------------------------- /src/vue/core/watcher.js: -------------------------------------------------------------------------------- 1 | import Dep from './dep' 2 | import queue from './queue' 3 | 4 | let watcherId = 0; 5 | export default class Watcher { 6 | constructor(fn, vm, isComputed = false, expression) { 7 | this.fn = fn; 8 | this.isComputed = isComputed; 9 | this.vm = vm; 10 | this.watcherId = ++watcherId; 11 | this.expression = expression; 12 | this.value = null; 13 | this.deps = [] 14 | if (isComputed) { 15 | this.dep = new Dep(); 16 | this.initComputed() 17 | }else { 18 | this.init() 19 | } 20 | } 21 | 22 | init() { 23 | const vm = this.vm; 24 | Dep.setTarget(this); 25 | if(this.expression){ 26 | this.value = vm[this.expression] 27 | }else { 28 | this.fn.call(vm) 29 | } 30 | Dep.setTarget(null); 31 | // } 32 | 33 | } 34 | 35 | initComputed() { 36 | } 37 | 38 | getValue() { 39 | if (this.isComputed && Dep.target) { 40 | this.dep.depend() 41 | } 42 | Dep.setTarget(this); 43 | let value = this.fn.call(this.vm) 44 | Dep.setTarget(null); 45 | return value 46 | } 47 | 48 | update() { 49 | const vm = this.vm; 50 | if (this.isComputed) { 51 | // console.log('computed更新了'); 52 | this.dep.notify(); 53 | } else if (this.expression) { 54 | // console.log('watcher更新了:' + this.expression); 55 | let oldValue = this.value; 56 | let newValue = vm[this.expression] 57 | this.fn.call(vm, oldValue, newValue) 58 | } else { 59 | queue(this); 60 | } 61 | } 62 | 63 | appendDep(dep) { 64 | this.deps.push(dep) 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/vue/index.js: -------------------------------------------------------------------------------- 1 | // 1.添加静态方法 2 | // 2. 添加实例方法 3 | // 3. 添加初始化函数 4 | import initGlobal from './core/global' 5 | import initInstance from './core/instance' 6 | import initConstruction from './core/construction' 7 | 8 | export default class Vue { 9 | constructor(options) { 10 | this.init(options) 11 | } 12 | } 13 | initGlobal(Vue); 14 | initInstance(Vue); 15 | initConstruction(Vue); 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/vue/utils/define-reactive.js: -------------------------------------------------------------------------------- 1 | import Dep from '../core/dep' 2 | import {observe} from '../core/init-state' 3 | 4 | export function defineReactive(obj, key) { 5 | 6 | if (key === '_ob_') { 7 | return 8 | } 9 | let value = obj[key]; 10 | const dep = new Dep(); 11 | Object.defineProperty(obj, key, { 12 | get() { 13 | let children = observe(value) 14 | if (Dep.target) { 15 | dep.depend() 16 | if (children) { 17 | children.dep.depend() 18 | } 19 | } 20 | return value 21 | }, 22 | set(newValue) { 23 | if (value === newValue) { 24 | return 25 | } 26 | value = newValue; 27 | observe(newValue); 28 | dep.notify() 29 | } 30 | }) 31 | } 32 | 33 | function dependArray(value) { 34 | 35 | } -------------------------------------------------------------------------------- /src/vue/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './next-tick' 2 | export * from './merge-options' 3 | export * from './normal' 4 | export * from './define-reactive' 5 | export * from './proxy' -------------------------------------------------------------------------------- /src/vue/utils/merge-options.js: -------------------------------------------------------------------------------- 1 | export function mergeOptions (parent,children,Component) { 2 | return children 3 | } -------------------------------------------------------------------------------- /src/vue/utils/next-tick.js: -------------------------------------------------------------------------------- 1 | let executeFunc 2 | let callBack = [] 3 | if (typeof Promise !== 'undefined') { 4 | executeFunc = () => { 5 | Promise.resolve().then(flushCallbacks) 6 | } 7 | } else { 8 | executeFunc = () => { 9 | setTimeout(flushCallbacks, 0) 10 | } 11 | } 12 | 13 | function flushCallbacks() { 14 | callBack.forEach(item => item()) 15 | callBack = [] 16 | } 17 | 18 | export function nextTick(cb, ctx) { 19 | callBack.push(cb.bind(ctx)) 20 | executeFunc() 21 | } -------------------------------------------------------------------------------- /src/vue/utils/normal.js: -------------------------------------------------------------------------------- 1 | export function isArray(arr) { 2 | return arr instanceof Array 3 | } 4 | 5 | export function isObject(obj) { 6 | return typeof obj === "object" 7 | } -------------------------------------------------------------------------------- /src/vue/utils/proxy.js: -------------------------------------------------------------------------------- 1 | export function proxy(obj, vm) { 2 | Object.keys(obj).forEach(key => { 3 | Object.defineProperty(vm, key, { 4 | get() { 5 | if(typeof obj[key] ==='function'){ 6 | return obj[key].bind(vm) 7 | }else { 8 | return obj[key] 9 | } 10 | }, 11 | set(value) { 12 | obj[key] = value 13 | } 14 | }) 15 | }) 16 | } -------------------------------------------------------------------------------- /src/vue/vnode/index.js: -------------------------------------------------------------------------------- 1 | //简单的vnode 差不多这些可以了 2 | export default class Vnode { 3 | constructor({tag, data, children, text, html, ele, context}) { 4 | this.tag = tag; 5 | this.data = data; 6 | this.children = children; 7 | this.text = text; 8 | this.html = html; 9 | this.dom = ele; 10 | this.context = context; 11 | this.key = data &&data.key 12 | } 13 | } 14 | 15 | export function createTextNode(text) { 16 | return new Vnode({text}) 17 | } 18 | 19 | export function createHtmlNode(html) { 20 | return new Vnode({html}) 21 | } 22 | 23 | export function createNormalNode(tag, data, children) { 24 | return new Vnode({tag, data, children}) 25 | 26 | } -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const {HashedModuleIdsPlugin} = require('webpack'); 5 | var os = require('os') 6 | const HappyPack = require('happypack'); 7 | const happyThreadPool = HappyPack.ThreadPool({ 8 | size: os.cpus().length 9 | }); 10 | module.exports = { 11 | entry: { 12 | index: ['webpack-hot-middleware/client?noInfo=true&reload=true',path.resolve(__dirname, 'src/index.js')] 13 | }, 14 | output: { 15 | path: path.resolve(__dirname, 'dist'), 16 | publicPath: "/", 17 | filename: 'assets/js/[name].[hash].js', 18 | chunkFilename: 'assets/js/[name].[hash].js' 19 | }, 20 | module: { 21 | rules: [{ 22 | test: /(\.jsx|\.js)$/, 23 | use: { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ["es2015"] 27 | } 28 | }, 29 | exclude: /node_modules/, 30 | include: '/src/' 31 | }, { 32 | test: /(\.css|\.scss|\.sass)$/, 33 | use: ['css-loader', 'sass-loader', { 34 | loader: 'postcss-loader', 35 | options: { 36 | plugins: () => [require('autoprefixer')({ 37 | 'browsers': ['> 1%', 'last 2 versions'] 38 | })] 39 | } 40 | 41 | }] 42 | }, { 43 | test: /\.(gif|jpg|png|ico)\??.*$/, 44 | use: { 45 | loader: 'url-loader', 46 | options: { 47 | limit: 1024, 48 | name: '[name].[ext]', 49 | publicPath: '../../', 50 | outputPath: 'assets/css/' 51 | } 52 | } 53 | }, { 54 | test: /\.(svg|woff|otf|ttf|eot)\??.*$/, 55 | use: { 56 | loader: 'url-loader', 57 | options: { 58 | limit: 1024, 59 | name: '[name].[ext]', 60 | publicPath: '../../', 61 | outputPath: 'assets/css/' 62 | } 63 | } 64 | }, { 65 | test: /\.html$/, 66 | use: { 67 | loader: 'html-loader', 68 | options: { 69 | minimize: true, 70 | removeComments: false, 71 | collapseWhitespace: false 72 | } 73 | } 74 | }] 75 | }, 76 | plugins: [ 77 | new HappyPack({ 78 | // loaders is the only required parameter: 79 | id: "js", 80 | loaders: ['babel-loader'], 81 | threadPool: happyThreadPool, 82 | verbose: true 83 | }), 84 | //清空dist 85 | new HashedModuleIdsPlugin(), 86 | new CleanWebpackPlugin(["dist"], { 87 | root: '', 88 | verbose: true, 89 | dry: false 90 | }), 91 | 92 | new HtmlWebpackPlugin({ 93 | template: './index.html', 94 | inject: 'body', 95 | hash: false 96 | }) 97 | 98 | ] 99 | }; -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const merge = require('webpack-merge'); 4 | const common = require('./webpack.common.js'); 5 | 6 | module.exports = merge(common, { 7 | mode: 'development', 8 | devtool: 'source-map', 9 | plugins: [ 10 | new webpack.NoEmitOnErrorsPlugin(), 11 | new webpack.HotModuleReplacementPlugin(), 12 | ] 13 | }); -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 6 | const merge = require('webpack-merge'); 7 | const common = require('./webpack.common.js'); 8 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 9 | module.exports = merge(common, { 10 | mode: 'production', 11 | plugins: [ 12 | new MiniCssExtractPlugin({ 13 | filename: 'assets/css/[name].[hash].min.css', 14 | chunkFilename: 'assets/css/[name].[hash].css' 15 | }) 16 | ], 17 | optimization: { 18 | minimizer: [ 19 | new UglifyJsPlugin({ 20 | sourceMap: true, 21 | uglifyOptions: { 22 | compress: { 23 | warnings: false, 24 | drop_console: true, 25 | booleans: false, 26 | collapse_vars: true, 27 | reduce_vars: true, 28 | loops: true 29 | }, 30 | output: { 31 | comments: false, 32 | beautify: false 33 | } 34 | } 35 | }), 36 | new OptimizeCssAssetsPlugin({ 37 | assetNameRegExp: /\.css$/, 38 | cssProcessor: require('cssnano')({zindex: false}), 39 | cssProcessorOptions: { 40 | discardComments: {removeAll: true} 41 | }, 42 | canPrint: false 43 | }, { 44 | copyUnmodified: true 45 | }) 46 | ] 47 | } 48 | }); --------------------------------------------------------------------------------