Skip to content

Commit

Permalink
Preserve top-level 'use server' directives like 'use client' dire…
Browse files Browse the repository at this point in the history
…ctives
  • Loading branch information
emmatown committed May 1, 2023
1 parent 4e72d99 commit 233ee25
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-weeks-sparkle.md
@@ -0,0 +1,5 @@
---
"@preconstruct/cli": patch
---

Top-level `'use server'` directives are now also preserved like `'use client'` directives.
86 changes: 86 additions & 0 deletions packages/cli/src/build/__tests__/other.ts
Expand Up @@ -1558,3 +1558,89 @@ test("simple use client with comment above directive", async () => {
`);
});

test("use server", async () => {
const dir = await testdir({
"package.json": JSON.stringify({
name: "pkg",
main: "dist/pkg.cjs.js",
module: "dist/pkg.esm.js",
}),
"src/index.js": js`
export { doSomething } from "./server";
`,
"src/server.js": js`
"use server";
export function doSomething() {}
`,
});
await build(dir);
expect(await getFiles(dir, ["dist/**"], stripHashes("server")))
.toMatchInlineSnapshot(`
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.cjs.dev.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var server = require('./server-some-hash.cjs.dev.js');
Object.defineProperty(exports, 'doSomething', {
enumerable: true,
get: function () { return server.doSomething; }
});
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
if (process.env.NODE_ENV === "production") {
module.exports = require("./pkg.cjs.prod.js");
} else {
module.exports = require("./pkg.cjs.dev.js");
}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var server = require('./server-some-hash.cjs.prod.js');
Object.defineProperty(exports, 'doSomething', {
enumerable: true,
get: function () { return server.doSomething; }
});
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
export { doSomething } from './server-some-hash.esm.js';
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/server-this-is-not-the-real-hash-288a9f5076a34272a0270a4055aa266d.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use server';
function doSomething() {}
export { doSomething };
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/server-this-is-not-the-real-hash-4c6b8ec0a8b072aff1b26a3ac24de144.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
if (process.env.NODE_ENV === "production") {
module.exports = require("./server-some-hash.cjs.prod.js");
} else {
module.exports = require("./server-some-hash.cjs.dev.js");
}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/server-this-is-not-the-real-hash-f9ea3f80de7afbb1e4ac2175565ef521.cjs.dev.js, dist/server-this-is-not-the-real-hash-f9ea3f80de7afbb1e4ac2175565ef521.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use server';
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function doSomething() {}
exports.doSomething = doSomething;
`);
});
50 changes: 25 additions & 25 deletions packages/cli/src/rollup-plugins/server-components.ts
Expand Up @@ -21,15 +21,15 @@ export function serverComponentsPlugin({
}
const loaded = await this.load(resolved);
if (
typeof loaded.meta.useClientReferenceId === "string" &&
typeof loaded.meta.directivePreservedFile?.referenceId === "string" &&
importer !== undefined
) {
// this name is appended for Rollup naming chunks/variables in the output
const name = path
.basename(resolved.id)
.replace(/\.[tj]sx?$/, "")
.replace(/[^\w]/g, "_");
const id = `__USE_CLIENT_IMPORT__${loaded.meta.useClientReferenceId}__USE_CLIENT_IMPORT__/${name}`;
const id = `__USE_CLIENT_IMPORT__${loaded.meta.directivePreservedFile.referenceId}__USE_CLIENT_IMPORT__/${name}`;

return {
id,
Expand All @@ -41,35 +41,35 @@ export function serverComponentsPlugin({
transform(code, id) {
if (id.startsWith("\0")) return null;
const directives = getModuleDirectives(code);
const useClientDirective = directives.find(
(d) => d.value === "use client"
const directive = directives.find(
(d) => d.value === "use client" || d.value === "use server"
);
if (useClientDirective) {
const magicString = new MagicString(code);
const referenceId = this.emitFile({
type: "chunk",
id,
preserveSignature: "allow-extension",
});
magicString.remove(useClientDirective.start, useClientDirective.end);
return {
code: magicString.toString(),
map: sourceMap
? (magicString.generateMap({ hires: true }) as SourceMapInput)
: undefined,
meta: {
useClientReferenceId: referenceId,
},
};
}
return null;
if (!directive) return null;
const magicString = new MagicString(code);
const referenceId = this.emitFile({
type: "chunk",
id,
preserveSignature: "allow-extension",
});
magicString.remove(directive.start, directive.end);
return {
code: magicString.toString(),
map: sourceMap
? (magicString.generateMap({ hires: true }) as SourceMapInput)
: undefined,
meta: {
directivePreservedFile: { referenceId, directive: directive.value },
},
};
},
renderChunk(code, chunk) {
const magicString = new MagicString(code);
if (chunk.facadeModuleId !== null) {
const moduleInfo = this.getModuleInfo(chunk.facadeModuleId);
if (moduleInfo?.meta.useClientReferenceId) {
magicString.prepend("'use client';\n");
if (moduleInfo?.meta.directivePreservedFile) {
const directive: "use client" | "use server" =
moduleInfo?.meta.directivePreservedFile.directive;
magicString.prepend(`'${directive}';\n`);
}
}

Expand Down
Expand Up @@ -100,7 +100,7 @@ export default function typescriptDeclarations(pkg: Package): Plugin {
const moduleInfo = this.getModuleInfo(file.facadeModuleId);
// this will happen for "use client" modules where it's not
// an actual entrypoint but it is a Rollup entry
if (moduleInfo?.meta.useClientReferenceId) {
if (moduleInfo?.meta.directivePreservedFile) {
continue;
}
// otherwise, a user should never be able to cause this to happen
Expand Down

0 comments on commit 233ee25

Please sign in to comment.