Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add use client module directive support #161

Merged
merged 1 commit into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
"rimraf": "^4.4.1",
"rollup": "^3.27.2",
"rollup-plugin-esbuild": "^5.0.0",
"rollup-plugin-preserve-directives": "^0.2.0",
"rxjs": "^7.8.1",
"treeify": "^1.1.0",
"uuid": "^9.0.0",
Expand Down
11 changes: 11 additions & 0 deletions playground/runtime-next-js/app/app-router/leaf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

// Teeny tiny component in its own file, demonstrating how small client component
// leafs can be in an RSC tree, and how little the impact of using `React.createContext` can be

import {useResult} from 'use-client-directive'

export default function Leaf() {
const result = useResult()
return <div>useContext={result}</div>

Check warning on line 10 in playground/runtime-next-js/app/app-router/leaf.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Expected blank line before this statement
}
18 changes: 18 additions & 0 deletions playground/runtime-next-js/app/app-router/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as index from 'dummy-module'
import * as extra from 'dummy-module/extra'

import Leaf from './leaf'

export default function IndexPage() {
return (
<div>
<div>
path={index.path}, format={index.format}, runtime={index.runtime}
</div>
<div>
path={extra.path}, format={extra.format}, runtime={extra.runtime}
</div>
<Leaf />
</div>
)
}
12 changes: 12 additions & 0 deletions playground/runtime-next-js/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Provider} from 'use-client-directive'

export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html lang="en">
<head />
<body>
<Provider>{children}</Provider>
</body>
</html>
)
}
1 change: 1 addition & 0 deletions playground/runtime-next-js/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
3 changes: 2 additions & 1 deletion playground/runtime-next-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dummy-module": "workspace:*",
"next": "^13.4.13",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"use-client-directive": "workspace:*"
},
"devDependencies": {
"@types/react": "^18.2.18"
Expand Down
6 changes: 4 additions & 2 deletions playground/runtime-next-js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "./tsconfig.settings",
"include": ["./next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["./next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["./node_modules"],
"compilerOptions": {
"noEmit": true,
Expand All @@ -11,6 +11,8 @@
"module": "esnext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"plugins": [{"name": "next"}],
"strictNullChecks": true
}
}
6 changes: 6 additions & 0 deletions playground/use-client-directive/package.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {defineConfig} from '@sanity/pkg-utils'

export default defineConfig({
tsconfig: 'tsconfig.dist.json',
preserveModuleDirectives: true,
})
32 changes: 32 additions & 0 deletions playground/use-client-directive/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "use-client-directive",
"version": "1.0.0",
"private": true,
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"source": "./src/index.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"scripts": {
"build": "run-s clean && pkg build --strict && pkg check --strict",
"clean": "rimraf dist"
},
"devDependencies": {
"@types/react": "^18.2.18",
"react": "^18.2.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
}
10 changes: 10 additions & 0 deletions playground/use-client-directive/src/_exports.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {createContext, useContext} from 'react'

const Context = createContext<'success' | 'failure'>('failure')

/** @public */
export const Provider = ({children}: {children: React.ReactNode}) => (
<Context.Provider value="success">{children}</Context.Provider>
)
/** @public */
export const useResult = () => useContext(Context)
3 changes: 3 additions & 0 deletions playground/use-client-directive/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use client'

export * from './_exports'
9 changes: 9 additions & 0 deletions playground/use-client-directive/tsconfig.dist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.settings",
"include": ["./src"],
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist",
"emitDeclarationOnly": true
}
}
8 changes: 8 additions & 0 deletions playground/use-client-directive/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.settings",
"include": ["./package.config.ts", "./src"],
"compilerOptions": {
"rootDir": ".",
"noEmit": true
}
}
30 changes: 30 additions & 0 deletions playground/use-client-directive/tsconfig.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"jsx": "preserve",

// Strict type-checking
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,

// Additional checks
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,

// Module resolution
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}
26 changes: 24 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/node/core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ export interface PkgConfigOptions {
* Build package with support for legacy exports (writes root `<export>.js` files)
*/
legacyExports?: boolean
/**
* Preserves module directives, such as `'use client'`
* @alpha
*/
preserveModuleDirectives?: boolean
minify?: boolean
/** @alpha */
rollup?: {
Expand Down
14 changes: 10 additions & 4 deletions src/node/tasks/rollup/resolveRollupConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import terser from '@rollup/plugin-terser'
import path from 'path'
import {InputOptions, OutputOptions, Plugin} from 'rollup'
import esbuild from 'rollup-plugin-esbuild'
import preserveDirectives from 'rollup-plugin-preserve-directives'

import {BuildContext, DEFAULT_BROWSERSLIST_QUERY, resolveConfigProperty} from '../../core'
import {RollupTask, RollupWatchTask} from '../types'
Expand Down Expand Up @@ -111,10 +112,12 @@ export function resolveRollupConfig(
}),
minify &&
terser({
compress: {
// Needed until https://github.com/terser/terser/issues/1320 is fixed
directives: false,
},
compress: config?.preserveModuleDirectives
? {
// Needed until https://github.com/terser/terser/issues/1320 is fixed
directives: false,
}
: true,
output: {
comments: (_node, comment) => {
const text = comment.value
Expand All @@ -130,6 +133,8 @@ export function resolveRollupConfig(
},
},
}),
// Allows marking export files with `'use client'` at the top
config?.preserveModuleDirectives && preserveDirectives(),
].filter(Boolean) as Plugin[]

const userPlugins = config?.rollup?.plugins
Expand Down Expand Up @@ -212,6 +217,7 @@ export function resolveRollupConfig(
minifyInternalExports: minify,
sourcemap: config?.sourcemap ?? true,
hoistTransitiveImports: false,
preserveModules: config?.preserveModuleDirectives ? true : false,
},
}
}
20 changes: 20 additions & 0 deletions src/node/tasks/rollup/rollupTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ async function execPromise(ctx: BuildContext, task: RollupTask) {
const bundle = await rollup({
...inputOptions,
onwarn(warning) {
if (
// Ignore the directive warning until the preserveDirectives plugin does it automatically: https://github.com/Ephem/rollup-plugin-preserve-directives#rollup-plugin-preserve-directives-warning
warning.message.includes(
'Module level directives cause errors when bundled, "use client" in',
)
) {
// If the preserve option isn't enabled, then extend the message with a note about how to enable
if (!ctx.config?.preserveModuleDirectives) {
consoleSpy.messages.push({
type: 'warn',
code: warning.code,
args: [
`${warning.message} You can preserve it by setting "preserveModuleDirectives: true" in your "package.config.ts"`,
],
})
}

return
}

consoleSpy.messages.push({
type: 'warn',
code: warning.code,
Expand Down