Skip to content

Commit

Permalink
feat: WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Amour1688 committed Jan 16, 2021
1 parent 2ac92f0 commit 10a1ded
Show file tree
Hide file tree
Showing 13 changed files with 4,374 additions and 90 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 天泽
Copyright (c) 2020 Chengzhang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
1 change: 1 addition & 0 deletions examples/A.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <div>23411dd</div>;
34 changes: 34 additions & 0 deletions examples/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { defineComponent } from 'vue';
import A from './A';
import { B } from './B';

const App = defineComponent({
data() {
return {
a: 1
}
},
render() {
const { a } = this;
return (
<>
{a}
<div onClick={() => { this.a++; }}>Hello World!!</div>
<A />
<B />
</>
)
}
});

export default App;

if (module.hot) {
App.__hmrId = "${id}"
const api = __VUE_HMR_RUNTIME__
module.hot.accept();
if (!api.createRecord('${id}', App)) {
api.reload('${id}', App)
}
api.rerender('${id}', App.render);
}
43 changes: 43 additions & 0 deletions examples/B.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineComponent } from 'vue';

const cache = {}

const B = defineComponent({
// setup() {
// const a = ref(0)
// return () => <div onClick={() => { a.value++; }}>{a.value}dssdddssd</div>
// }
data() {
return {
a: 1
}
},
render() {
const { a } = this;
return (
<>
<div onClick={() => { this.a++; }}>{a}d4s</div>
<span>23dd</span>
</>
);
}
});

export {
B
};

if (module.hot) {
B.__hmrId = "b"
const api = __VUE_HMR_RUNTIME__
module.hot.accept();
if (!module.hot.data) {
api.createRecord('b', B);
} else if (cache.b === B.render) {
api.rerender('b', B.render);
} else {
api.reload('b', B)
}

cache.b = JSON.stringify(B.render);
}
4 changes: 4 additions & 0 deletions examples/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createApp } from 'vue';
import App from './App';

createApp(App).mount('#app');
11 changes: 11 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/app.js"></script>
</body>
</html>
20 changes: 16 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"version": "1.0.0",
"description": "Tweak Vue components written in JSX in real time.",
"main": "dist/index.js",
"scripts": {},
"scripts": {
"dev": "webpack-dev-server",
"build": "tsc"
},
"files": [
"dist"
],
Expand All @@ -24,13 +27,22 @@
"homepage": "https://github.com/Amour1688/vue-jsx-hot-loader#readme",
"dependencies": {
"@babel/parser": "^7.0.0",
"@babel/template": "^7.0.0",
"@babel/traverse": "^7.0.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
"loader-utils": "^2.0.0",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"@types/webpack": "^4.41.22",
"@babel/core": "^7.12.10",
"@types/loader-utils": "^2.0.1",
"@vue/babel-plugin-jsx": "^1.0.0",
"babel-loader": "^8.2.2",
"jest": "^26.6.3",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
"vue": "^3.0.5",
"webpack": "^4.44.2",
"webpack-cli": "^3.0.0",
"webpack-dev-server": "^3.11.1"
}
}
25 changes: 0 additions & 25 deletions src/hotReload.ts

This file was deleted.

135 changes: 128 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,133 @@
import webpack from 'webpack';
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import * as webpack from 'webpack';
import * as hash from 'hash-sum';
import * as path from 'path';
import * as loaderUtils from 'loader-utils';
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { File } from '@babel/types'
import { parse } from "@babel/parser";
import { isDefineComponentCall, parseComponentDecls } from './utils';

const hasJSX = (file: File) => {
let fileHasJSX = false;
traverse(file, {
JSXElement(path) {
fileHasJSX = true;
path.stop();
},
JSXFragment(path) {
fileHasJSX = true;
path.stop();
},
});

return fileHasJSX;
};

export default function loader(
this: webpack.loader.LoaderContext,
source: string
): string {
source: string,
sourceMap: string
) {
const loaderContext = this;

return source;
loaderContext.cacheable?.();
const isDev = process.env.NODE_ENV === 'development';

if (isDev) {
loaderContext.callback(null, source, sourceMap);
}

const file = parse(source);

if (!hasJSX(file)) {
loaderContext.callback(null, source, sourceMap);
return;
}

const webpackRemainingChain = loaderUtils.getRemainingRequest(loaderContext).split('!');
const fullPath = webpackRemainingChain[webpackRemainingChain.length - 1];
const filename = path.relative(process.cwd(), fullPath);

const declaredComponents: { name: string }[] = [];
const hotComponents: {
local: string;
exported: string;
id: string;
}[] = [];
let defaultIdentifier: t.Identifier | null = null;

traverse(file, {
VariableDeclaration(nodePath) {
declaredComponents.push(...parseComponentDecls(nodePath.node));
},
ExportNamedDeclaration(nodePath) {
const { specifiers = [], declaration } = nodePath.node;
if (t.isVariableDeclaration(declaration)) {
hotComponents.push(...parseComponentDecls(declaration).map(({ name }) => ({
local: name,
exported: name,
id: hash(`${filename}-${name}`),
})));
} else if (specifiers.length) {
for (const spec of specifiers) {
if (t.isExportSpecifier(spec) && t.isIdentifier(spec.exported)) {
if (declaredComponents.find(d => d.name === spec.local.name)) {
hotComponents.push({
local: spec.local.name,
exported: spec.exported.name,
id: hash(`${filename}-${spec.exported.name}`)
});
}
}
}
}
},
ExportDefaultDeclaration(nodePath) {
const { declaration } = nodePath.node;
if (t.isIdentifier(declaration)) {
if (declaredComponents.find(d => d.name === declaration.name)) {
hotComponents.push({
local: declaration.name,
exported: 'default',
id: hash(`${filename}-default`)
})
}
} else if (isDefineComponentCall(declaration)) {
defaultIdentifier = nodePath.scope.generateUidIdentifier('default')
hotComponents.push({
local: defaultIdentifier.name,
exported: 'default',
id: hash(`${filename}-default`)
});
}
}
});

if (hotComponents.length) {
if (defaultIdentifier) {
const { name } = defaultIdentifier as t.Identifier;
source.replace(
/export default defineComponent/g,
`const ${name} = defineComponent`
) + `\nexport default ${name}`
}

let callbackCode = '';
for (const { local, exported, id } of hotComponents) {
source +=
`\n${local}.__hmrId = '${id}'` +
`\n__VUE_HMR_RUNTIME__.createRecord('${id}', ${local})`
callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", __${exported})`
}

source += `
/* hot reload */
if (module.hot) {
module.hot.accept()
${callbackCode}
}
`
}

loaderContext.callback(null, source, sourceMap);
};
1 change: 1 addition & 0 deletions src/shim.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'hash-sum'
22 changes: 18 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
export function isFuntionalComponent(
code: string
): boolean {
return !!code;
import { Node } from '@babel/core';
import * as t from '@babel/types';

export function isDefineComponentCall(node?: Node | null) {
return t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === 'defineComponent';
}

export function parseComponentDecls(node: t.VariableDeclaration) {
const names = [];
for (const decl of node.declarations) {
if (t.isIdentifier(decl.id) && isDefineComponentCall(decl.init)) {
names.push({
name: decl.id.name
});
}
}

return names;
}
35 changes: 35 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const path = require('path');

const babelConfig = {
plugins: [
'@vue/babel-plugin-jsx'
],
}

module.exports = {
mode: 'development',
entry: {
app: './examples/index.js',
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
options: babelConfig,
}
],
},
devServer: {
historyApiFallback: true,
hot: true,
open: true
},
resolve: {
extensions: ['.jsx', '.js']
}
};
Loading

0 comments on commit 10a1ded

Please sign in to comment.