├── .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 | 
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 | });
--------------------------------------------------------------------------------