从零开始创建一项目骨架
项目目录
npm init -y
npm install webpack webpack-cli html-webpack-plugin css-loader style-loader less-loader less file-loader clean-webpack-plugin extract-text-webpack-plugin webpack-merge webpack-dev-server -D
安装好依赖项,我这里简单做了下webpack dev prod合并
webpack.base.config.js
// 生产和开发环境公用部分
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin') // 新版
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
module.exports = {
entry:{
index:'./src/index.js'
},
module: {
rules:[
{
test:/\.css$/,
use:ExtractTextWebpackPlugin.extract({
fallback:'style-loader',
use:'css-loader'
})
},
{
test:/\.less$/,
use:ExtractTextWebpackPlugin.extract({
fallback:'style-loader',
use:['css-loader','less-loader']
})
},
{
test:/\.(jpg|png|svg)$/,
loader:['file-loader']
},
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
}),
new ExtractTextWebpackPlugin("style.css"),
new CleanWebpackPlugin(),
]
}
webpack.dev.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const webpackMerge = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const baseConfig = require('./webpack.base.config')
const dist_dir = 'dev_dist'
module.exports = webpackMerge(baseConfig,{
mode:'development',
output: {
path:path.resolve(__dirname,dist_dir)
},
devServer: { // 开发环境运行 浏览器端口9000
contentBase: path.join(__dirname, dist_dir),
compress: true,
port: 9000
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template:'./src/index.html',
}),
new ExtractTextWebpackPlugin("style.css"),
]
})
webpack…prod.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const dist_dir = 'prod_dist'
module.exports = webpackMerge(baseConfig,{
mode:'production',
output: {
path:path.resolve(__dirname,dist_dir)
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
title:"diff dev"
}),
new ExtractTextWebpackPlugin("style.css"),
]
})
package.json
{
"name": "webpack-diff",
"version": "1.0.0",
"description": "",
"main": "webpack.dev.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "webpack-dev-server --open --config webpack.dev.config.js",
"build": "webpack --config webpack.prod.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0", // 注意版本
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"style-loader": "^1.1.2",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-merge": "^4.2.2"
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
hello world
</div>
</body>
</html>
index.css
#app{
color:red
}
#app span{
font-size: 16px;
}
index.js
import './index.css'
function main() {
console.log('测试运行')
}
main()
npm run serve
至此一个简单的webpack工程项目建立起来了。现在我们开始正题。
先简述模板转换成视图的过程
虚拟DOM
Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。
类似于以下结构,
{
type:"" //标签
key:"",
props:{},
{},
{}, // 作为子节点
}
vdom/index.js
import createElement from './h.js'
import { render} from './patch'
export {
createElement,
render
}
vnode/h.js
import { vnode } from './vnode'
export default function createElement(type, props = {}, ...children) {
let key;
if (props.key) {
key = props.key;
delete props.key;
}
// 将不是虚拟节点的子节点
children =children.map(child=>{
if(typeof child==='string'){
return vnode(undefined,undefined,undefined,undefined,child)
}else {
return child
}
})
return vnode(type, key, props, children)
}
vnode/vnode.js
export function vnode(type, key, props, children, text){
return {
type,
key,
props,
children,
text
}
}
vnode/patch.js
/**
* @param {} vnode // 用户写的虚拟节点
* @param {} container // 渲染到某个容器
*/
export function render(vnode, container) {
let ele = createDomElementFromVnode(vnode);
container.appendChild(ele)
}
// 通过虚拟DOM创建真实的DOM
function createDomElementFromVnode(vnode) {
let {
type,
key,
props,
children,
text
} = vnode;
if (type) { // 说明是一个标签
vnode.domElement = document.createElement(type);
updateProperties(vnode) //根据当前的虚拟节点的属性,更新真实的DOM
// 遍历子节点
children.forEach(childVnode => render(childVnode, vnode.domElement))
} else {
vnode.domElement = document.createTextNode(text)
}
return vnode.domElement
}
// 后续比对的时候,会根据老的属性 和 新的属性 重新更新节点
function updateProperties(newVnode, oldProps = {}) {
let domElement = newVnode.domElement; //真实的dom元素
let newProps = newVnode.props; // 当前虚拟节点的属性
// 属性如果老的里面有,新的没有 将移除
for (let oldPropName in oldProps) {
if (!newProps[oldPropName]) {
delete domElement[oldPropName]
}
}
// 同一 style属性,旧得移除 新的覆盖
let newStyleObj = newProps.style || {}
let oldStyleObj = newProps.style || {}
for (let propName in oldStyleObj) {
if (!newStyleObj[propName]) {
domElement.style[propName] = ''
}
}
// 如果老的没有,新的里面有 将覆盖老节点
for (let newPropName in newProps) {
if (newPropName === 'style') { // 还有事件
let styleObj = newProps.style;
for (let s in styleObj) {
domElement.style[s] = styleObj[s]
}
} else {
domElement[newPropName] = newProps[newPropName]
}
}
}
测试
src/index.js
import { createElement, render } from './vdom'
let vnode = createElement('div',{id:'wrap1',key:'4'},createElement('div',{id:'wrap2',key:'3'},'测试二'),'测试一')
render(vnode,app) // app会自动识别#app