Skip to content

Commit

Permalink
feat: add use client module directive support
Browse files Browse the repository at this point in the history
  • Loading branch information
stipsan committed Aug 7, 2023
1 parent 898c785 commit 66bb0e1
Show file tree
Hide file tree
Showing 18 changed files with 206 additions and 9 deletions.
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

0 comments on commit 66bb0e1

Please sign in to comment.