Skip to content

Commit 45bef9e

Browse files
committed
feat: add packageJson setting
- closes #7258 - closes #6824 - closes #4828 - part of #2242 - better message for #2884
1 parent 5571e4a commit 45bef9e

File tree

23 files changed

+233
-48
lines changed

23 files changed

+233
-48
lines changed

.changeset/nine-spoons-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/package': minor
3+
---
4+
5+
feat: add `packageJson` setting to adjust final `package.json`

documentation/docs/30-advanced/70-packaging.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
title: Packaging
33
---
44

5-
> `svelte-package` is currently experimental. Non-backward compatible changes may occur in any future release.
6-
75
You can use SvelteKit to build apps as well as component libraries, using the `@sveltejs/package` package (`npm create svelte` has an option to set this up for you).
86

97
When you're creating an app, the contents of `src/routes` is the public-facing stuff; [`src/lib`](modules#$lib) contains your app's internal library.
@@ -12,11 +10,11 @@ A component library has the exact same structure as a SvelteKit app, except that
1210

1311
Running the `svelte-package` command from `@sveltejs/package` will take the contents of `src/lib` and generate a `package` directory (which can be [configured](configuration)) containing the following:
1412

15-
- All the files in `src/lib`, unless you [configure](configuration) custom `include`/`exclude` options. Svelte components will be preprocessed, TypeScript files will be transpiled to JavaScript.
16-
- Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is. You can [disable generation](configuration), but we strongly recommend against it — people using your library might use TypeScript, for which they require these type definition files.
17-
- A `package.json` copied from the project root with all fields except `"scripts"`, `"publishConfig.directory"` and `"publishConfig.linkDirectory"`. The `"dependencies"` field is included, which means you should add packages that you only need for your documentation or demo site to `"devDependencies"`. A `"type": "module"` and an `"exports"` field will be added if it's not defined in the original file.
13+
- All the files in `src/lib`, unless you [configure](configuration) the custom `files` option. Svelte components will be preprocessed, TypeScript files will be transpiled to JavaScript.
14+
- Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is. You can [disable generation](configuration) by setting `emitTypes: false`, but we strongly recommend against it — people using your library might use TypeScript, for which they require these type definition files.
15+
- A `package.json` copied from the project root with all fields except `"scripts"`, `"publishConfig.directory"` and `"publishConfig.linkDirectory"`. The `"dependencies"` field is included, which means you should add packages that you only need for your documentation or demo site to `"devDependencies"`. A `"type": "module"` and an `"exports"` field will be added if it's not defined in the original file. You can customize the final `package.json` contents through the `packageJson` option, which is passed the original and generated `package.json`. If you return `undefined`, the `package.json` will not be written to the output directory.
1816

19-
The `"exports"` field contains the package's entry points. By default, all files in `src/lib` will be treated as an entry point unless they start with (or live in a directory that starts with) an underscore, but you can [configure](configuration) this behaviour. If you have a `src/lib/index.js` or `src/lib/index.svelte` file, it will be treated as the package root.
17+
The `"exports"` field contains the package's entry points. By default, all files in `src/lib` will be treated as an entry point unless they start with (or live in a directory that starts with) an underscore, but you can [configure](configuration) this behaviour through the `packageJson` option. If you have a `src/lib/index.js` or `src/lib/index.svelte` file, it will be treated as the package root.
2018

2119
For example, if you had a `src/lib/Foo.svelte` component and a `src/lib/index.js` module that re-exported it, a consumer of your library could do either of the following:
2220

packages/package/src/config.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,26 @@ export async function load_config({ cwd = process.cwd() } = {}) {
2424
* @returns {import('./types').ValidatedConfig}
2525
*/
2626
function process_config(config, { cwd = process.cwd() } = {}) {
27+
let warned = false;
28+
2729
return {
2830
extensions: config.extensions ?? ['.svelte'],
2931
kit: config.kit,
3032
package: {
3133
source: path.resolve(cwd, config.kit?.files?.lib ?? config.package?.source ?? 'src/lib'),
3234
dir: config.package?.dir ?? 'package',
33-
exports: config.package?.exports ?? ((filepath) => !/^_|\/_|\.d\.ts$/.test(filepath)),
35+
exports: config.package?.exports
36+
? (filepath) => {
37+
if (!warned) {
38+
console.warn(
39+
'The `package.exports` option is deprecated. Use `package.packageJson` instead.'
40+
);
41+
warned = true;
42+
}
43+
return /** @type {any} */ (config.package).exports(filepath);
44+
}
45+
: (filepath) => !/^_|\/_|\.d\.ts$/.test(filepath),
46+
packageJson: config.package?.packageJson ?? ((_, pkg) => pkg),
3447
files: config.package?.files ?? (() => true),
3548
emitTypes: config.package?.emitTypes ?? true
3649
},

packages/package/src/index.js

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import colors from 'kleur';
44
import chokidar from 'chokidar';
55
import { preprocess } from 'svelte/compiler';
66
import { copy, mkdirp, rimraf } from './filesystem.js';
7-
import { analyze, generate_pkg, resolve_lib_alias, scan, strip_lang_tags, write } from './utils.js';
7+
import {
8+
analyze,
9+
generate_pkg,
10+
resolve_lib_alias,
11+
scan,
12+
strip_lang_tags,
13+
write,
14+
write_if_changed
15+
} from './utils.js';
816
import { emit_dts, transpile_ts } from './typescript.js';
917

1018
const essential_files = ['README', 'LICENSE', 'CHANGELOG', '.gitignore', '.npmignore'];
@@ -30,39 +38,12 @@ export async function build(config, cwd = process.cwd()) {
3038
await emit_dts(config, cwd, files);
3139
}
3240

33-
const pkg = generate_pkg(cwd, files);
41+
const { pkg, pkg_name } = generate_pkg(cwd, config.package.packageJson, files);
3442

35-
if (!pkg.dependencies?.svelte && !pkg.peerDependencies?.svelte) {
36-
console.warn(
37-
'Svelte libraries should include "svelte" in either "dependencies" or "peerDependencies".'
38-
);
43+
if (pkg) {
44+
write_if_changed(join(dir, 'package.json'), JSON.stringify(pkg, null, 2));
3945
}
4046

41-
if (!pkg.svelte && files.some((file) => file.is_svelte)) {
42-
// Several heuristics in Kit/vite-plugin-svelte to tell Vite to mark Svelte packages
43-
// rely on the "svelte" property. Vite/Rollup/Webpack plugin can all deal with it.
44-
// See https://github.com/sveltejs/kit/issues/1959 for more info and related threads.
45-
if (pkg.exports['.']) {
46-
const svelte_export =
47-
typeof pkg.exports['.'] === 'string'
48-
? pkg.exports['.']
49-
: pkg.exports['.'].import || pkg.exports['.'].default;
50-
if (svelte_export) {
51-
pkg.svelte = svelte_export;
52-
} else {
53-
console.warn(
54-
'Cannot generate a "svelte" entry point because the "." entry in "exports" is not a string. If you set it by hand, please also set one of the options as a "svelte" entry point\n'
55-
);
56-
}
57-
} else {
58-
console.warn(
59-
'Cannot generate a "svelte" entry point because the "." entry in "exports" is missing. Please specify one or set a "svelte" entry point yourself\n'
60-
);
61-
}
62-
}
63-
64-
write(join(dir, 'package.json'), JSON.stringify(pkg, null, 2));
65-
6647
for (const file of files) {
6748
await process_file(config, file);
6849
}
@@ -83,8 +64,10 @@ export async function build(config, cwd = process.cwd()) {
8364
const from = relative(cwd, lib);
8465
const to = relative(cwd, dir);
8566
console.log(colors.bold().green(`${from} -> ${to}`));
86-
console.log(`Successfully built '${pkg.name}' package. To publish it to npm:`);
87-
console.log(colors.bold().cyan(` cd ${to}`));
67+
console.log(`Successfully built '${pkg_name}' package. To publish it to npm:`);
68+
if (pkg) {
69+
console.log(colors.bold().cyan(` cd ${to}`));
70+
}
8871
console.log(colors.bold().cyan(' npm publish\n'));
8972
}
9073

@@ -98,8 +81,7 @@ export async function watch(config, cwd = process.cwd()) {
9881

9982
console.log(message);
10083

101-
const { source: lib } = config.package;
102-
const { dir } = config.package;
84+
const { dir, source: lib } = config.package;
10385

10486
/** @type {Array<{ file: import('./types').File, type: string }>} */
10587
const pending = [];
@@ -129,7 +111,7 @@ export async function watch(config, cwd = process.cwd()) {
129111
pending.length = 0;
130112

131113
for (const { file, type } of events) {
132-
if ((type === 'unlink' || type === 'add') && file.is_exported) {
114+
if (type === 'unlink' || type === 'add') {
133115
should_update_pkg = true;
134116
}
135117

@@ -161,9 +143,11 @@ export async function watch(config, cwd = process.cwd()) {
161143
}
162144

163145
if (should_update_pkg) {
164-
const pkg = generate_pkg(cwd, files);
165-
write(join(dir, 'package.json'), JSON.stringify(pkg, null, 2));
166-
console.log('Updated package.json');
146+
const pkg = generate_pkg(cwd, config.package.packageJson, files);
147+
const changed = write_if_changed(join(dir, 'package.json'), JSON.stringify(pkg, null, 2));
148+
if (changed) {
149+
console.log('Updated package.json');
150+
}
167151
}
168152

169153
if (config.package.emitTypes) {

packages/package/src/utils.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ export function write(file, contents) {
6666
fs.writeFileSync(file, contents);
6767
}
6868

69+
/** @type {Map<string, string>} */
70+
let current = new Map();
71+
/**
72+
* @param {string} file
73+
* @param {string} contents
74+
*/
75+
export function write_if_changed(file, contents) {
76+
if (current.get(file) !== contents) {
77+
write(file, contents);
78+
current.set(file, contents);
79+
return true;
80+
}
81+
return false;
82+
}
83+
6984
/**
7085
* @param {import('./types').ValidatedConfig} config
7186
* @returns {import('./types').File[]}
@@ -106,10 +121,12 @@ export function analyze(config, file) {
106121

107122
/**
108123
* @param {string} cwd
124+
* @param {NonNullable<import('types').PackageConfig['packageJson']>} packageJson
109125
* @param {import('./types').File[]} files
110126
*/
111-
export function generate_pkg(cwd, files) {
127+
export function generate_pkg(cwd, packageJson, files) {
112128
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
129+
const original = JSON.parse(JSON.stringify(pkg));
113130

114131
// Remove fields that are specific to the original package.json
115132
// See: https://pnpm.io/package_json#publishconfigdirectory
@@ -147,5 +164,37 @@ export function generate_pkg(cwd, files) {
147164
}
148165
}
149166

150-
return pkg;
167+
if (!pkg.dependencies?.svelte && !pkg.peerDependencies?.svelte) {
168+
console.warn(
169+
'Svelte libraries should include "svelte" in either "dependencies" or "peerDependencies".'
170+
);
171+
}
172+
173+
if (!pkg.svelte && files.some((file) => file.is_svelte)) {
174+
// Several heuristics in Kit/vite-plugin-svelte to tell Vite to mark Svelte packages
175+
// rely on the "svelte" property. Vite/Rollup/Webpack plugin can all deal with it.
176+
// See https://github.com/sveltejs/kit/issues/1959 for more info and related threads.
177+
if (pkg.exports['.']) {
178+
const svelte_export =
179+
typeof pkg.exports['.'] === 'string'
180+
? pkg.exports['.']
181+
: pkg.exports['.'].svelte || pkg.exports['.'].import || pkg.exports['.'].default;
182+
if (svelte_export) {
183+
pkg.svelte = svelte_export;
184+
} else {
185+
console.warn(
186+
'Cannot generate a "svelte" entry point because the "." entry in "exports" is not a string. If you set it by hand, please also set one of the options as a "svelte" entry point in your package.json\n' +
187+
'Example: { ..., "svelte": "./index.svelte" } }\n'
188+
);
189+
}
190+
} else {
191+
console.warn(
192+
'Cannot generate a "svelte" entry point because the "." entry in "exports" is missing. Please specify one or set a "svelte" entry point yourself in your package.json\n' +
193+
'Example: { ..., "svelte": "./index.svelte" } }\n'
194+
);
195+
}
196+
}
197+
198+
const final = packageJson(original, pkg);
199+
return { pkg: packageJson(original, pkg), pkg_name: final?.name ?? original.name };
151200
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo: true;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = true;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "package-json-omit",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "omits package json",
6+
"type": "module"
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = true;

0 commit comments

Comments
 (0)