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 b94ff9d
Show file tree
Hide file tree
Showing 14 changed files with 4,355 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 example/A.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <div>23411dd</div>;
24 changes: 24 additions & 0 deletions example/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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;
22 changes: 22 additions & 0 deletions example/B.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineComponent } from 'vue';

const B = defineComponent({
data() {
return {
a: 1
}
},
render() {
const { a } = this;
return (
<>
<div onClick={() => { this.a++; }}>{a}d4s</div>
<span>2</span>
</>
);
}
});

export {
B
};
4 changes: 4 additions & 0 deletions example/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');
43 changes: 43 additions & 0 deletions example/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const path = require("path");

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

module.exports = {
mode: "development",
entry: {
app: path.resolve(__dirname, "./index.js"),
},
output: {
path: path.resolve(__dirname, "./dist"),
publicPath: "/dist/",
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [
{
loader: "babel-loader",
options: babelConfig,
},
"vue-jsx-hot-loader",
],
},
],
},
devServer: {
historyApiFallback: true,
hot: true,
open: true,
},
resolve: {
extensions: [".jsx", ".js"],
},
resolveLoader: {
alias: {
"vue-jsx-hot-loader": require.resolve("../"),
},
},
};
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>
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testTimeout: 30000,
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/'],
}
23 changes: 19 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
"version": "1.0.0",
"description": "Tweak Vue components written in JSX in real time.",
"main": "dist/index.js",
"scripts": {},
"types": "dist/index.d.ts",
"scripts": {
"dev": "tsc -w",
"build": "tsc",
"test": "jest --coverage",
"dev-example": "webpack-dev-server --config example/webpack.config.js",
"prepublishOnly": "tsc"
},
"files": [
"dist"
],
Expand All @@ -26,11 +33,19 @@
"@babel/parser": "^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.

130 changes: 123 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,128 @@
import webpack from 'webpack';
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import * as webpack from 'webpack';
import 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, { sourceType: 'module', plugins: ['jsx', 'typescript'] });

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;
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,
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,
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,
id: hash(`${filename}-default`)
})
}
} else if (isDefineComponentCall(declaration)) {
defaultIdentifier = nodePath.scope.generateUidIdentifier('default')
hotComponents.push({
local: defaultIdentifier.name,
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, id } of hotComponents) {
source +=
`\n${local}.__hmrId = '${id}'` +
`\n__VUE_HMR_RUNTIME__.createRecord('${id}', ${local})`
callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", ${local})`
}

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;
}
Loading

0 comments on commit b94ff9d

Please sign in to comment.