Skip to content

Commit

Permalink
Fix 'use client' directives with comments preceding them not being …
Browse files Browse the repository at this point in the history
…detected
  • Loading branch information
emmatown committed May 1, 2023
1 parent 93106e3 commit 4e72d99
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-otters-turn.md
@@ -0,0 +1,5 @@
---
"@preconstruct/cli": patch
---

Fix `'use client'` directives with comments preceding them not being detected
94 changes: 94 additions & 0 deletions packages/cli/src/build/__tests__/other.ts
Expand Up @@ -1464,3 +1464,97 @@ test("import use client self as entrypoint", async () => {
`);
});

test("simple use client with comment above directive", 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 { A } from "./client";
`,
"src/client.js": js`
/** blah */
"use client";
export const A = "something";
console.log("client");
`,
});
await build(dir);
expect(await getFiles(dir, ["dist/**"], stripHashes("client")))
.toMatchInlineSnapshot(`
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/client-this-is-not-the-real-hash-309cc5e233da5126cc473e58b428ae77.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
if (process.env.NODE_ENV === "production") {
module.exports = require("./client-some-hash.cjs.prod.js");
} else {
module.exports = require("./client-some-hash.cjs.dev.js");
}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/client-this-is-not-the-real-hash-d42256f03593be08d8620d4b6456d377.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use client';
/** blah */
const A = "something";
console.log("client");
export { A };
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/client-this-is-not-the-real-hash-ef1aedc2ed504d143f108943a7d13c16.cjs.dev.js, dist/client-this-is-not-the-real-hash-ef1aedc2ed504d143f108943a7d13c16.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use client';
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/** blah */
const A = "something";
console.log("client");
exports.A = A;
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.cjs.dev.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var client = require('./client-some-hash.cjs.dev.js');
Object.defineProperty(exports, 'A', {
enumerable: true,
get: function () { return client.A; }
});
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 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 client = require('./client-some-hash.cjs.prod.js');
Object.defineProperty(exports, 'A', {
enumerable: true,
get: function () { return client.A; }
});
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/pkg.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
export { A } from './client-some-hash.esm.js';
`);
});
46 changes: 46 additions & 0 deletions packages/cli/src/rollup-plugins/directives.test.ts
@@ -0,0 +1,46 @@
import { Directive, getModuleDirectives } from "./directives";

function remove(str: string, directive: Directive) {
return str.slice(0, directive.start) + str.slice(directive.end);
}

test("basic", () => {
const input = `"use strict";blah`;
const result = getModuleDirectives(input);
expect(result).toEqual([{ value: "use strict", start: 0, end: 13 }]);
expect(remove(input, result[0]).toString()).toEqual("blah");
});

test("without semi", () => {
const input = `"use strict"\nblah`;
const result = getModuleDirectives(input);
expect(result).toEqual([{ value: "use strict", start: 0, end: 12 }]);
expect(remove(input, result[0]).toString()).toEqual("\nblah");
});

test("escaped directive then real directive", () => {
const input = `'use \\'client';'use client';blah`;
const result = getModuleDirectives(input);
expect(result).toEqual([
{ end: 15, start: 0, value: "use \\'client" },
{ end: 28, start: 15, value: "use client" },
]);
expect(remove(input, result[0]).toString()).toEqual("'use client';blah");
expect(remove(input, result[1]).toString()).toEqual("'use \\'client';blah");
});

test("block comments", () => {
const input = `/** @jsx jsx\n */\n'use client';blah`;
const result = getModuleDirectives(input);
expect(result).toEqual([{ value: "use client", start: 17, end: 30 }]);
expect(remove(input, result[0]).toString()).toEqual(
`/** @jsx jsx\n */\nblah`
);
});

test("line comments", () => {
const input = `// something\n'use client';blah`;
const result = getModuleDirectives(input);
expect(result).toEqual([{ value: "use client", start: 13, end: 26 }]);
expect(remove(input, result[0]).toString()).toEqual(`// something\nblah`);
});
30 changes: 16 additions & 14 deletions packages/cli/src/rollup-plugins/directives.ts
@@ -1,6 +1,6 @@
const whitespace = /\s/;

type Directive = {
export type Directive = {
value: string;
start: number;
end: number;
Expand All @@ -15,30 +15,32 @@ export function getModuleDirectives(source: string): Directive[] {
continue;
}
if (char === "/") {
if (source[i + 1] === "/") {
i += 2;
while (
i < source.length &&
(source[i] !== "\r" || source[i] !== "\n")
) {
i++;
if (source[i] === "/") {
while (i < source.length && source[i] !== "\r" && source[i] !== "\n") {
i++;
}
continue;
}
if (source[i + 1] === "*") {
i += 2;
if (source[i] === "*") {
while (
i < source.length &&
(source[i] !== "*" || source[i + 1] !== "/")
) {
i++;
}
i += 2;
i += 1;
continue;
}
break;
}
if (char === ";" && lastDirectiveExpectingSemi !== undefined) {
lastDirectiveExpectingSemi.end = i + 1;
lastDirectiveExpectingSemi = undefined;
continue;
if (char === ";") {
if (lastDirectiveExpectingSemi !== undefined) {
lastDirectiveExpectingSemi.end = i + 1;
lastDirectiveExpectingSemi = undefined;
continue;
}
break;
}
if (char === '"' || char === "'") {
let start = i;
Expand Down

0 comments on commit 4e72d99

Please sign in to comment.