Skip to content

Commit bb1a928

Browse files
authored
feat: create vite-null-export package (#105)
1 parent 1e85e26 commit bb1a928

File tree

8 files changed

+164
-0
lines changed

8 files changed

+164
-0
lines changed

packages/demo/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@hiogawa/utils-hattip": "0.0.1-pre.1",
3232
"@hiogawa/vite-glob-routes": "workspace:*",
3333
"@hiogawa/vite-import-dev-server": "workspace:*",
34+
"@hiogawa/vite-null-export": "workspace:*",
3435
"@iconify-json/ri": "^1.1.9",
3536
"@tanstack/react-query": "^4.29.14",
3637
"@tanstack/react-query-devtools": "^4.29.14",

packages/demo/vite.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import process from "node:process";
22
import globRoutesPlugin from "@hiogawa/vite-glob-routes";
33
import { importDevServerPlugin } from "@hiogawa/vite-import-dev-server";
4+
import { viteNullExportPlugin } from "@hiogawa/vite-null-export";
45
import vaviteConnect from "@vavite/connect";
56
import react from "@vitejs/plugin-react";
67
import unocss from "unocss/vite";
@@ -18,6 +19,10 @@ export default defineConfig((ctx) => ({
1819
handlerEntry:
1920
process.env["SERVER_ENTRY"] ?? "./src/server/adapter-connect.ts",
2021
}),
22+
viteNullExportPlugin({
23+
serverOnly: "**/server/**",
24+
debug: true,
25+
}),
2126
],
2227
build: {
2328
outDir: ctx.ssrBuild ? "dist/server" : "dist/client",

packages/vite-null-export/README.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# vite-null-export
2+
3+
Vite port of remix's server-only file convention
4+
5+
- https://remix.run/docs/en/main/file-conventions/-server
6+
- https://remix.run/docs/en/main/guides/gotchas#server-code-in-client-bundles
7+
- https://github.com/remix-run/remix/blob/80c6842f547b7e83b58f1963894b07ad18c2dfe2/packages/remix-dev/compiler/plugins/emptyModules.ts#L10
8+
9+
## example
10+
11+
```tsx
12+
// vite.config.ts
13+
14+
import { defineConfig } from "vite";
15+
import { viteNullExportPlugin } from "@hiogawa/vite-null-export";
16+
17+
export default defineConfig({
18+
plugins: [
19+
viteNullExportPlugin({
20+
serverOnly: ["**/server/**", "**/*.server.*"],
21+
}),
22+
],
23+
});
24+
```
25+
26+
## development
27+
28+
```sh
29+
pnpm build
30+
pnpm release
31+
```
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@hiogawa/vite-null-export",
3+
"version": "0.0.0",
4+
"homepage": "https://github.com/hi-ogawa/vite-plugins/tree/main/packages/vite-null-export",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/hi-ogawa/vite-plugins",
8+
"directory": "packages/vite-null-export"
9+
},
10+
"license": "MIT",
11+
"type": "module",
12+
"exports": {
13+
".": {
14+
"import": "./dist/index.js",
15+
"require": "./dist/index.cjs",
16+
"types": "./dist/index.d.ts"
17+
}
18+
},
19+
"main": "./dist/index.js",
20+
"module": "./dist/index.js",
21+
"types": "./dist/index.d.ts",
22+
"files": [
23+
"dist"
24+
],
25+
"scripts": {
26+
"build": "tsup",
27+
"release": "pnpm publish --no-git-checks --access public"
28+
},
29+
"dependencies": {
30+
"es-module-lexer": "^1.3.1"
31+
},
32+
"devDependencies": {
33+
"vite": "^4.4.4"
34+
},
35+
"peerDependencies": {
36+
"vite": "*"
37+
}
38+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as esModuleLexer from "es-module-lexer";
2+
import {
3+
type FilterPattern,
4+
type Plugin,
5+
type ResolvedConfig,
6+
createFilter,
7+
} from "vite";
8+
import { name as packageName } from "../package.json";
9+
10+
// similar to remix's https://github.com/remix-run/remix/blob/80c6842f547b7e83b58f1963894b07ad18c2dfe2/packages/remix-dev/compiler/plugins/emptyModules.ts#L10
11+
// but for vite, we needs to fake esm exports so we use es-module-lexer to extract export names.
12+
13+
export function viteNullExportPlugin(pluginOpts?: {
14+
serverOnly?: FilterPattern;
15+
serverOnlyExclude?: FilterPattern;
16+
clientOnly?: FilterPattern;
17+
clientOnlyExclude?: FilterPattern;
18+
debug?: boolean;
19+
}): Plugin {
20+
const serverOnly = pluginOpts?.serverOnly
21+
? createFilter(pluginOpts?.serverOnly, pluginOpts?.serverOnlyExclude)
22+
: () => false;
23+
const clientOnly = pluginOpts?.clientOnly
24+
? createFilter(pluginOpts?.clientOnly, pluginOpts?.clientOnlyExclude)
25+
: () => false;
26+
27+
let logger!: ResolvedConfig["logger"];
28+
29+
return {
30+
// we don't enforce "pre" since es-module-lexer cannot reliably parse typescript file.
31+
// so we rely on vite's default typescript transpilation.
32+
33+
name: packageName,
34+
35+
configResolved(config) {
36+
logger = config.logger;
37+
},
38+
39+
async transform(code, id, options) {
40+
if (options?.ssr ? clientOnly(id) : serverOnly(id)) {
41+
if (pluginOpts?.debug) {
42+
logger.info(
43+
`[DEBUG:${packageName}:${
44+
options?.ssr ? "clientOnly" : "serverOnly"
45+
}] ${id}`
46+
);
47+
}
48+
await esModuleLexer.init;
49+
const [_import, exports] = esModuleLexer.parse(code);
50+
return exports
51+
.map((e) =>
52+
e.n === "default"
53+
? `export default null;\n`
54+
: `export var ${e.n} = null;\n`
55+
)
56+
.join("");
57+
}
58+
return undefined;
59+
},
60+
};
61+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"include": ["src"]
4+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts"],
5+
format: ["esm", "cjs"],
6+
dts: true,
7+
});

pnpm-lock.yaml

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)