From 45d24bc88fb1c5f93ace06b08ead6e77b8a273d8 Mon Sep 17 00:00:00 2001 From: Yiyi Sun Date: Mon, 16 Sep 2024 00:59:53 -0400 Subject: [PATCH] redo dev mode --- .gitignore | 1 + apprun-site-cli.js | 14 +-- demo/tailwind.config.js | 2 +- package-lock.json | 248 ++++++++++++++-------------------------- package.json | 4 +- server.js | 65 +++-------- src/build-css.js | 53 ++++----- src/build-ts.js | 44 ++++--- src/build.js | 121 +++++++++----------- src/esbuild.js | 18 +-- src/vfs.js | 11 +- ws.js | 14 ++- 12 files changed, 243 insertions(+), 352 deletions(-) diff --git a/.gitignore b/.gitignore index b3f6e91..b89ce39 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage .cache src/_lib public +._build diff --git a/apprun-site-cli.js b/apprun-site-cli.js index 6775adc..3ddca9b 100755 --- a/apprun-site-cli.js +++ b/apprun-site-cli.js @@ -36,13 +36,12 @@ program .option('-w, --watch', 'watch the directory', false) .option('-o, --output [output]', 'output directory', 'public') .option('-p, --pages [pages]', 'pages directory', 'pages') - .option('-i, --info', 'print option information', false) .option('--no-csr', 'no client side routing') .option('-r --render', 'pre-render pages', false) .action(async (source, options) => { ({ source, options } = await init_options(source, options)); - options.info && console.log(options); - build(options); + options.dev = false; + await build(options); }); program @@ -51,28 +50,21 @@ program .option('-o, --output [output]', 'output directory', 'public') .option('--no-ssr', 'disable server side rendering') .option('--no-save', 'disable auto save of side rendered pages') - .option('-i, --info', 'print option information', false) .action(async (source, options) => { ({ source, options } = await init_options(source, options)); - options.info && console.log(options); - // build(options); server(options); }); program .command('dev [source]') .description('launch development server, watch and live reload') - .option('-c, --clean', 'clean the output directory', false) .option('-o, --output [output]', 'output directory', 'public') .option('-p, --pages [pages]', 'pages directory', 'pages') - .option('--no-ssr', 'disable server side rendering') - .option('--no-csr', 'no client side routing') .option('--no-watch', 'watch the directory') .option('--no-live_reload', 'enable live reload') - .option('-i, --info', 'print option information', false) + .option('--no-csr', 'no client side routing') .action(async (source, options) => { ({ source, options } = await init_options(source, options)); - options.info && console.log(options); options.ssr = false; options.save = false; options.dev = true; diff --git a/demo/tailwind.config.js b/demo/tailwind.config.js index c306028..eef6ff9 100644 --- a/demo/tailwind.config.js +++ b/demo/tailwind.config.js @@ -1,7 +1,7 @@ /** @type {import('tailwindcss').Config} */ export default { content: [ - 'public/**/*.{html,css}' + 'pages/**/*.{html,css,jsx,tsx}' ], theme: { extend: {}, diff --git a/package-lock.json b/package-lock.json index 9c2ff8f..58785f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "chokidar": "^3.6.0", "commander": "^12.1.0", "esbuild": "^0.23.1", - "express": "^4.20.0", + "express": "^4.21.0", "jsdom": "^25.0.0", "markdown-it": "^14.1.0", "postcss-load-config": "^6.0.1", @@ -29,7 +29,7 @@ }, "peerDependencies": { "apprun": "^3.33.8", - "postcss": "^8.4.45" + "postcss": "^8.4.47" } }, "node_modules/@colors/colors": { @@ -825,21 +825,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1522,9 +1507,9 @@ } }, "node_modules/express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -1539,7 +1524,7 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -1548,11 +1533,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1650,13 +1635,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1676,6 +1661,15 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2569,9 +2563,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "license": "ISC", "peer": true }, @@ -2587,9 +2581,9 @@ } }, "node_modules/postcss": { - "version": "8.4.45", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", - "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -2608,8 +2602,8 @@ "peer": true, "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2704,12 +2698,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -2942,63 +2936,27 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "node_modules/serve-static/node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, "node_modules/set-function-length": { @@ -3075,9 +3033,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "peer": true, "engines": { @@ -3883,14 +3841,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } } } }, @@ -4357,9 +4307,9 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -4373,7 +4323,7 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -4382,11 +4332,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -4464,12 +4414,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -4485,6 +4435,11 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5080,9 +5035,9 @@ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "peer": true }, "picomatch": { @@ -5091,14 +5046,14 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "postcss": { - "version": "8.4.45", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", - "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "peer": true, "requires": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" } }, "postcss-load-config": { @@ -5140,11 +5095,11 @@ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "querystringify": { @@ -5289,55 +5244,20 @@ } }, "serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - } + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" } } }, @@ -5394,9 +5314,9 @@ } }, "source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "peer": true }, "stack-trace": { diff --git a/package.json b/package.json index 81c7b93..b040986 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "chokidar": "^3.6.0", "commander": "^12.1.0", "esbuild": "^0.23.1", - "express": "^4.20.0", + "express": "^4.21.0", "jsdom": "^25.0.0", "markdown-it": "^14.1.0", "postcss-load-config": "^6.0.1", @@ -44,7 +44,7 @@ }, "peerDependencies": { "apprun": "^3.33.8", - "postcss": "^8.4.45" + "postcss": "^8.4.47" }, "devDependencies": { "@eslint/js": "^9.10.0", diff --git a/server.js b/server.js index 7c0ef87..b009133 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,6 @@ import { existsSync, statSync, writeFileSync, mkdirSync } from 'fs'; import { join, resolve } from 'path'; -import { readFileSync } from 'fs'; import express from 'express'; import bodyParser from 'body-parser'; import render from './src/render.js'; @@ -13,7 +12,7 @@ export let config = {}; export default function (_config = {}) { const cwd = process.cwd(); - let { source, output, ssr, root, port, save, live_reload } = _config; + let { source, output, ssr, root, port, save, live_reload, dev } = _config; root = output || root || 'public'; root = resolve(cwd, root); source = resolve(cwd, source || '.'); @@ -21,7 +20,7 @@ export default function (_config = {}) { save = save === undefined ? true : save; if (!ssr) save = false; if (port === undefined) port = 8080; - config = { source, output, ssr, root, port, save, live_reload }; + config = { ..._config, source, output, ssr, root, port, save, live_reload }; const app = express(); app.use(bodyParser.json()); @@ -29,7 +28,7 @@ export default function (_config = {}) { set_api(app, source); set_action(app, source); - set_vfs(app, root); + if (dev) set_vfs(app, root); set_ssr(app, root, port, ssr, save); app.use((err, req, res, next) => { @@ -43,53 +42,17 @@ export default function (_config = {}) { export function set_vfs(app, root) { app.use((req, res, next) => { - const reqPath = join(root, req.path); - if (vfs.has(reqPath)) { - const asset = vfs.get(reqPath); - res.set('Content-Type', asset.type); - res.send(asset.content); - return; + const reqPath = req.path; + const indexPath = '/index.html'; + const asset = vfs.get(reqPath) || vfs.get(indexPath); // alwasy SPA mode + if (asset) { + res.type('.' + asset.type).send(asset.content); + } else { + next(); } - next(); }); } -// export function send_live_reload(req, res, next) { -// const reqPath = req.path; -// if (reqPath.endsWith('.html') || reqPath === '/') { -// const filePath = join(root, reqPath === '/' ? 'index.html' : reqPath); -// if (existsSync(filePath)) { -// const data = readFileSync(filePath, 'utf8'); -// const injectedData = data.replace( -// /<\/body>/i, -// `` -// ); -// res.type('.html').send(injectedData); -// return; -// } -// } -// next(); -// } - export function set_ssr(app, root, port, ssr, save) { app.get('*', async (req, res, next) => { @@ -138,8 +101,12 @@ export function set_ssr(app, root, port, ssr, save) { } } const home_html = `${root}/_.html`; - info('Serve:', '/._html', '(SPA)'); - res.sendFile(home_html); + if (existsSync(home_html)) { + info('Serve:', '/._html', '(SPA)'); + res.sendFile(home_html); + } else { + res.sendStatus(404); + } } } catch (err) { diff --git a/src/build-css.js b/src/build-css.js index d81ab25..5defd27 100644 --- a/src/build-css.js +++ b/src/build-css.js @@ -2,35 +2,35 @@ import { existsSync, writeFileSync, readFileSync, copyFileSync } from 'fs'; import chalk from 'chalk'; const { cyan, yellow, blue, green, magenta, gray, red } = chalk; -import { exec } from 'child_process'; +// import { exec } from 'child_process'; import vfs from './vfs.js'; -export const build_tailwind = async (from, to, config) => { - const { source, pages, output, relative, should_ignore } = config; - const tailwind = `${source}/tailwind.config.js`; - if (!existsSync(from)) return; - if (!existsSync(tailwind)) { - console.log(cyan('Copied CSS'), relative(to)); - copyFileSync(from, to); - return; - } - return new Promise((resolve, reject) => { - exec(`npx tailwindcss -i ${from} -o ${to} -c ${tailwind}`, { cwd: source }, (err) => { - if (err) { - reject(err); - } - console.log(cyan('Compiled CSS with Tailwindcss'), relative(to)); - resolve(to); - }) - }) -}; +// export const build_tailwind = async (from, to, config) => { +// const { source, pages, output, relative, should_ignore } = config; +// const tailwind = `${source}/tailwind.config.js`; +// if (!existsSync(from)) return; +// if (!existsSync(tailwind)) { +// console.log(cyan('Copied CSS'), relative(to)); +// copyFileSync(from, to); +// return; +// } +// return new Promise((resolve, reject) => { +// exec(`npx tailwindcss -i ${from} -o ${to} -c ${tailwind}`, { cwd: source }, (err) => { +// if (err) { +// reject(err); +// } +// console.log(cyan('Compiled CSS with Tailwindcss'), relative(to)); +// resolve(to); +// }) +// }) +// }; export const build_css = async (from, to, config) => { const { source, relative } = config; try { const postcss = `${source}/postcss.config.js`; - if (!existsSync(from)) return; - if (!existsSync(postcss)) return build_tailwind(from, to, config); + if (!existsSync(postcss)) return; + // if (!existsSync(postcss)) return build_tailwind(from, to, config); const css = readFileSync(from, 'utf8'); const context = { from, to, cwd: source, map: true } @@ -45,12 +45,13 @@ export const build_css = async (from, to, config) => { console.warn(err.toString()); } - - vfs.set(to, result.css, 'text/css'); + if(config.dev) { + vfs.set(relative(to), result.css, 'css'); + } else { + writeFileSync(to, result.css); + } // notifyChange('css', filePath); - writeFileSync(to, result.css); - console.log(cyan('Compiled CSS with PostCss'), relative(to)); } catch (err) { diff --git a/src/build-ts.js b/src/build-ts.js index ebbe404..fdec115 100644 --- a/src/build-ts.js +++ b/src/build-ts.js @@ -1,6 +1,6 @@ /* eslint-disable no-console */ //@ts-check -import { writeFileSync, existsSync, unlinkSync, copyFileSync } from 'fs'; +import { writeFileSync, existsSync, unlinkSync, copyFileSync, readFileSync } from 'fs'; import path from 'path'; import chalk from 'chalk'; const { cyan, green, magenta, gray} = chalk; @@ -8,14 +8,14 @@ import esbuild, { bundle } from './esbuild.js'; export let routes = []; -export const build_component = async (content, target) => { +export const build_component = async (content, target, config) => { const html = content.replace(/`/g, '\\`'); const component = `const {safeHTML} = window; export default () => safeHTML(\`${html}\`);`; const tsx_file = target.replace(/\.[^/.]+$/, '.tsx'); if (!tsx_file.endsWith('index.tsx')) return; writeFileSync(tsx_file, component); - await esbuild(tsx_file, target); + await esbuild(tsx_file, target, config); unlinkSync(tsx_file); }; @@ -80,7 +80,7 @@ ${init ? import_main : 'export default () => {}'} } const components = ${JSON.stringify(routes)}; const route = (path) => { - let bestMatch; + let bestMatch = '/'; let longestMatchLength = 0; components.forEach((item, index) => { const _route = item[0]; @@ -99,7 +99,7 @@ app.route = null; window.onload = async () => { app.route = route; components.map(item => add_component(item, '${site_url}')); - app.route(${!csr ? 'loacation.hash' : 'location.pathname'}); + app.route(${!csr ? 'location.hash' : 'location.pathname'}); }; ${csr ? ` document.body.addEventListener('click', e => { @@ -129,7 +129,7 @@ function _init_refresh() { const address = protocol + window.location.host + window.location.pathname + '/ws'; const socket = new WebSocket(address); socket.onmessage = function (msg) { - const {event, path} = JSON.parse(msg.data); + const path = JSON.parse(msg.data); if(path.endsWith('.css')) { reload_css(location.protocol + '//' + location.host + path); } else { @@ -145,19 +145,31 @@ window.addEventListener('DOMContentLoaded', _init_refresh); // const main_no_csr = init ? import_main : 'export default () => {}'; writeFileSync(tsx_file, main); - await esbuild(tsx_file, main_js_file); - // unlinkSync(tsx_file); - console.log(green('Created main file'), 'main.js', + await esbuild(tsx_file, main_js_file, config); + unlinkSync(tsx_file); + console.log(green('Created main.js'), relative(main_js_file), magenta(`(live reload: ${live_reload || false}, client side rendering: ${csr || false})`)); + if (!config.dev) { + + await run_bundle(config); + + const pages_index_html = `${config.pages}/index.html`; + const main_index_html = `${config.output}/_.html`; + if (existsSync(pages_index_html)) { + const content = readFileSync(pages_index_html, 'utf8'); + writeFileSync(main_index_html, content); + console.log(green('Copied index.html to '), relative(main_index_html)); + } - const server_js_file = `${source}/server.js`; - if (!existsSync(server_js_file)) { - const server_fn = new URL('./server.js', import.meta.url); - copyFileSync(server_fn, server_js_file); - console.log(green('Created server file'), relative(server_js_file)); - } else { - console.log(gray('Server file exists, skipped'), relative(server_js_file)); + const server_js_file = `${source}/server.js`; + if (!existsSync(server_js_file)) { + const server_fn = new URL('./server.js', import.meta.url); + copyFileSync(server_fn, server_js_file); + console.log(green('Created server file'), relative(server_js_file)); + } else { + console.log(gray('Server file exists, skipped'), relative(server_js_file)); + } } }; diff --git a/src/build.js b/src/build.js index e3143ca..9664b87 100644 --- a/src/build.js +++ b/src/build.js @@ -1,44 +1,36 @@ /* eslint-disable no-console */ //@ts-check -import { existsSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync, renameSync } from 'fs'; -import { readdir, stat } from 'fs/promises'; -import { join, dirname, basename, extname } from 'path'; -import path from 'path'; +import { existsSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync, readFileSync, readdirSync } from 'fs'; +import { join, dirname, basename, extname, relative as path_relative } from 'path'; import chokidar from 'chokidar'; import chalk from 'chalk'; const { cyan, yellow, magenta, red, green } = chalk; import esbuild from './esbuild.js'; -import { build_main, build_component, add_route, routes, run_bundle } from './build-ts.js'; +import { build_main, build_component, add_route, routes } from './build-ts.js'; import { build_css } from './build-css.js'; import { markdown } from './build-md.js'; import render from './render.js'; +import vfs from './vfs.js'; const Markdown_Types = ['.md', '.mdx']; const Esbuild_Types = ['.js', '.jsx', '.ts', '.tsx']; const Copy_Types = ['.html', '.htm', '.png', '.gif', '.json', '.svg', '.jpg', '.jpeg', '.ico']; const Css_Types = ['.css']; -// const css_files = []; -let need_bundle = false; const ensure = dir => { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); }; -export const should_ignore = (src, dest) => { - if (!existsSync(dest)) return false; - const src_stat = statSync(src); - const dest_stat = statSync(dest); - return src_stat.mtimeMs <= dest_stat.mtimeMs; -} async function walk(dir, config) { - let files = await readdir(dir); - await Promise.all(files.map(async file => { + let files = readdirSync(dir); + + for(const file of files) { const filePath = join(dir, file); - const stats = await stat(filePath); - if (stats.isDirectory()) return walk(filePath, config); - else if (stats.isFile()) return process_file(filePath, config); - })); + const stats = statSync(filePath); + if (stats.isDirectory()) await walk(filePath, config); + else if (stats.isFile()) await process_file(filePath, config); + } } const render_routes = async ({ output, relative}) => { @@ -65,7 +57,6 @@ const run_build = async (config) => { routes.length = 0; await walk(config.pages, config); await build_main(config); - // await run_bundle(config); const elapsed = Date.now() - start_time; console.log(cyan(`Build done in ${elapsed} ms.`)); } @@ -79,11 +70,11 @@ const debounce = (func, delay) => { }; const onChange = (async (config, path) => { - // console.log(yellow('Change detected'), relative(path)); - await walk(dirname(path), config); - if (need_bundle) { - need_bundle = false; - await run_bundle(config); + const ext = extname(path); + if (Css_Types.indexOf(ext) >= 0) { + await process_file(path, config); + } else { + await run_build(config); } }); @@ -92,38 +83,40 @@ const debouncedOnChange = debounce(onChange, 300); let copy_files; export default async (config) => { - const { source, pages, output, assets, relative, clean } = config; - const is_production = process.env.NODE_ENV === 'production'; + const { source, pages, output, assets, relative, clean , dev } = config; - console.log(`${cyan('Build from')} ${yellow(relative(pages))} to ${yellow(relative(output))} ${is_production ? cyan('for production') : cyan('for development')}`); - if (clean) { + console.log(`${cyan('Build from')} ${yellow(relative(pages))} to ${yellow(relative(output))} ${dev ? cyan('DEV mode') : ''}`); + + if (config.dev) vfs.clean(); + if (!config.dev && clean) { rmSync(output, { recursive: true, force: true }); console.log(cyan('Clean'), relative(output)); } - config.should_ignore = should_ignore; - Array.isArray(assets) && Copy_Types.push(...assets); copy_files = [...new Set(Copy_Types)]; try { - // const _relative = config.relative; - // const _output = config.output; - // const build_dir = join(source, '.__site'); - // config.output = build_dir; - // config.relative = fn => '/' + path.relative(build_dir, fn); - await run_build(config); - // rmSync(output, { recursive: true, force: true }); - // renameSync(build_dir, _output); - // config.output = _output; - // config.relative = _relative; - + if (config.dev) { + const _relative = config.relative; + const _output = config.output; + const build_dir = join(source, '._build'); + config.output = build_dir; + config.relative = fn => '/' + path_relative(build_dir, fn); + await run_build(config); + rmSync(build_dir, { recursive: true, force: true }); + config.output = _output; + config.relative = _relative; + } else { + await run_build(config); + } if (config.render) { const start_time = Date.now(); await render_routes(config); const elapsed = Date.now() - start_time; console.log(cyan(`Render done in ${elapsed} ms.`)); } + } catch (e) { console.log(red('Build failed'), e.message); } @@ -146,51 +139,43 @@ export default async (config) => { } async function process_file(file, config) { - const { pages, output, plugins, relative } = config; + const { pages, output, relative } = config; const dir = dirname(file).replace(pages, ''); const name = basename(file).replace(/\.[^/.]+$/, ''); const ext = extname(file); const pub_dir = join(output, dir); ensure(pub_dir); + const js_file = join(output, dir, name) + '.js'; if (copy_files.indexOf(ext) >= 0) { let dest = join(pub_dir, name) + ext; - if (dest === join(output, 'index.html')) dest = join(output, '_.html'); - if (!should_ignore(file, dest)) { + if (config.dev) { + const content = readFileSync(file, 'utf-8'); + vfs.set(relative(dest), content, ext.replace('.', '')); + } else { copyFileSync(file, dest); - console.log(cyan('Copied File'), relative(dest)); } + console.log(cyan('Copied File'), relative(dest)); } else if (Markdown_Types.indexOf(ext) >= 0) { - if (!should_ignore(file, js_file)) { - need_bundle = true; - const content = markdown(file) - if (!content) { - console.log(red('Markdown load failed')); - } else { - await build_component(content, js_file); - console.log(cyan('Created Component'), relative(js_file)); - } + + const content = markdown(file) + if (!content) { + console.log(red('Markdown load failed')); + } else { + await build_component(content, js_file, config); + console.log(cyan('Created Component'), relative(js_file)); } add_route(dir, js_file, output); } else if (Esbuild_Types.indexOf(ext) >= 0) { - if (!should_ignore(file, js_file)) { - need_bundle = true; - await esbuild(file, js_file); - console.log(cyan('Compiled JavaSript'), relative(js_file)); - } + await esbuild(file, js_file, config); + console.log(cyan('Compiled JavaSript'), relative(js_file)); + // } add_route(dir, js_file, output); } else if (Css_Types.indexOf(ext) >= 0) { const css_file = join(output, dir, name) + '.css'; - if (!should_ignore(file, css_file)) { - // css_files.push([file, css_file]); - await build_css(file, css_file, config); - } + await build_css(file, css_file, config); } else { console.log(magenta('Unknown file type'), relative(file)); } - - if (plugins && plugins[ext]) { - await plugins[ext](file, config); - } } diff --git a/src/esbuild.js b/src/esbuild.js index 9ef909d..bddf762 100644 --- a/src/esbuild.js +++ b/src/esbuild.js @@ -1,13 +1,18 @@ +//@ts-check /* eslint-disable no-console */ import esbuild from 'esbuild'; import chalk from 'chalk'; const { cyan, yellow, blue, green, magenta, gray, red } = chalk; import conditionalCompilePlugin from './esbuild-plugin.js'; import vfs from './vfs.js'; +import { send } from '../ws.js'; -export default build_in_memory; -export async function build(file, target, options = {}) { +export default function (file, target, config) { + return config.dev ? build_in_memory(file, target, config) : build(file, target); +} + +export async function build(file, target) { try { const result = await esbuild.build({ entryPoints: [file], @@ -16,7 +21,6 @@ export async function build(file, target, options = {}) { bundle: false, sourcemap: true, minify: false, - ...options, }); result.errors.length && console.log(red(result.errors)); result.warnings.length && console.log(yellow(result.warnings)); @@ -25,7 +29,7 @@ export async function build(file, target, options = {}) { } } -export async function build_in_memory (file, target, options = {}) { +export async function build_in_memory(file, target, config) { try { const result = await esbuild.build({ entryPoints: [file], @@ -34,7 +38,6 @@ export async function build_in_memory (file, target, options = {}) { bundle: true, sourcemap: true, minify: false, - ...options, write: false, }); result.errors.length && console.log(red(result.errors)); @@ -44,9 +47,10 @@ export async function build_in_memory (file, target, options = {}) { result.outputFiles.forEach(f => { const filePath = f.path; const moduleKey = filePath.replace(/\\/g, '/'); - vfs.set(moduleKey, f.text, 'application/javascript;charset=UTF-8'); + const relativePath = config.relative(filePath); + vfs.set(relativePath, f.text, 'js'); }) - // notifyChange('js', filePath); + send(target); } } catch (e) { console.log(red(e.message)); diff --git a/src/vfs.js b/src/vfs.js index 8b88f02..a8b0e6f 100644 --- a/src/vfs.js +++ b/src/vfs.js @@ -11,13 +11,12 @@ export const get = (key) => assets.get(key); export const has = (key) => assets.has(key); -export const dump = (outputDir) => { +export const list = () => { for (const [key, asset] of assets.entries()) { - const { content } = asset; - const outputPath = path.join(outputDir, key); - fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, content); + const { type } = asset; + + console.log('vfs', key, type); } } -export default { has, set, get, clean, dump }; \ No newline at end of file +export default { has, set, get, clean, list }; \ No newline at end of file diff --git a/ws.js b/ws.js index 79f92a7..f97472a 100644 --- a/ws.js +++ b/ws.js @@ -2,9 +2,19 @@ import http from 'http'; import WebSocket from 'ws'; import { info, error } from './src/log.js'; +let wss; + +export const send = message => { + if(wss) wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify(message)); + } + }); +} + export default app => { const server = http.createServer(app); - const wss = new WebSocket.Server({ server }); + wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { info('WS:', 'New WebSocket connection established'); ws.on('message', async function (message) { @@ -53,5 +63,5 @@ export default app => { info('WS:', 'WebSocket connection closed'); }); }); - return { server, wss }; + return { server, wss, send }; } \ No newline at end of file