sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
18.48k stars 1.89k forks source link

Quick but dirty way to move svelte.config.js and tsconfig.json to subdirectory #8858

Closed weirdo-neutrino closed 1 year ago

weirdo-neutrino commented 1 year ago

Describe the problem

Monorepo problems

project/
├── backend/
├── frontend/
├── svelte.config.js
├── tsconfig.json
├── vite.config.ts
└── package.json

Describe the proposed solution

project/
├── backend/
├── frontend/
│   ├── svelte.config.js
│   ├── tsconfig.json
│   └── vite.config.ts
└── package.json
// package.json

// I have done it with passing `--project` option
{
    "scripts": {
        "f:sync":    "svelte-kit sync                --project frontend",
        "f:dev":     "npm run f:sync && vite dev     --project frontend --config frontend/vite.config.js",
        "f:build":   "npm run f:sync && vite build   --project frontend --config frontend/vite.config.js",
        "f:preview": "                  vite preview --project frontend --config frontend/vite.config.js"
    }
}
// frontend/svelte.config.js

import adapter from '@sveltejs/adapter-node'

export default {
    kit: {
        adapter: adapter({
            out: 'build/frontend'
        }),
        files: {
            assets:        'frontend/static',
            hooks: {
                client:    'frontend/hooks.client',
                server:    'frontend/hooks.server',
            },
            lib:           'frontend/src/lib',
            params:        'frontend/src/params',
            routes:        'frontend/src/routes',
            serviceWorker: 'frontend/src/service-worker',
            appTemplate:   'frontend/src/app.html',
            errorTemplate: 'frontend/src/error.html'
        },
        outDir: 'build/.cache/frontend'
    }
}
// frontend/vite.config.ts

import { defineConfig } from 'vite'
import { sveltekit } from '@sveltejs/kit/vite'

export default defineConfig({
    mode: process.env.VITEST,
    plugins: [
        sveltekit()
    ]
})
// frontend/tsconfig.json

{
    "extends": "../build/.cache/frontend/tsconfig.json"
}

Alternatives considered

This example in no way shows how it must be implemented in production. It's just a proof of concept and a starting point to make a patch for those who want this feature today. I have updated SvelteKit and Vite code directly inside node_modules to quickly make it work. There are not so much code so I just copied parts I modified with comments describing essential parts.

// Files I had to update
node_modules/
├── @sveltejs/
│   ├── kit/
│   │   ├── src/
│   │   │   ├── core/
│   │   │   │   ├── config/index.js
│   │   │   │   ├── postbuild/analyse.js
│   │   │   │   ├── sync/write_tsconfig.js
│   │   │   ├── exports/vite/index.js
│   │   │   ├── cli.js
├── vite/dist/node/cli.js
// node_modules/@sveltejs/kit/src/core/config/index.js

export async function load_config() {
    // Dirty way to take `--project` option value to make it work
    const index = process.argv.indexOf('--project');
    const directory = index !== -1 ? process.argv[index + 1] : '.';
    const filename = path.resolve(directory, 'svelte.config.js');

    if (!fs.existsSync(filename)) {
        // No longer uses cwd parameter
        return process_config({});
    }

    const config = await import(`${url.pathToFileURL(filename).href}?ts=${Date.now()}`);

    return process_config(config.default);
}

// No longer uses cwd parameter
function process_config(config) {
    const validated = validate_config(config);

    validated.kit.outDir = path.resolve(validated.kit.outDir);

    for (const key in validated.kit.files) {
        if (key === 'hooks') {
            validated.kit.files.hooks.client = path.resolve(validated.kit.files.hooks.client);
            validated.kit.files.hooks.server = path.resolve(validated.kit.files.hooks.server);
        } else {
            validated.kit.files[key] = path.resolve(validated.kit.files[key]);
        }
    }

    return validated;
}

// node_modules/@sveltejs/kit/src/core/postbuild/analyse.js

// Analyse process seems to be forked so `process.argv` is no longer valid.
// Passing required kit fields solves the problem.
async function analyse({ manifest_path, env, outDir, publicPrefix }) {
    // skipped

    const server_root = join(outDir, 'output');

    // skipped

    const prefix = publicPrefix;

    // skipped
}

// node_modules/@sveltejs/kit/src/core/sync/write_tsconfig.js

export function write_tsconfig(kit) {
    // Dirty way to take `--project` option value to make it work
    const index = process.argv.indexOf('--project');
    const directory = index !== -1 ? process.argv[index + 1] : '.';
    const filename = path.resolve(directory, 'tsconfig.json');

    const out = path.join(kit.outDir, 'tsconfig.json');

    const user_config = load_user_tsconfig(filename);
    if (user_config) {
        validate_user_config(kit, path.dirname(filename), out, user_config);
    }

    // skipped
}

function load_user_tsconfig(filename) {
    if (!filename) return;

    const json = fs.readFileSync(filename, 'utf-8');

    return {
        kind: path.basename(filename),
        options: (0, eval)(`(${json})`)
    };
}

// node_modules/@sveltejs/kit/src/exports/vite/index.js

// Analyse process seems to be forked so `process.argv` is no longer valid.
// Passing required kit fields solves the problem.
const metadata = await analyse({
    manifest_path,
    env: { ...env.private, ...env.public },
    outDir: kit.outDir,
    publicPrefix: kit.env.publicPrefix
});

// node_modules/@sveltejs/kit/src/cli.js

prog
    // skipped
    // SvelteKit CLI requires to document new option
    .option('--project', 'Project root', '.')
    .action(async ({ mode, project }) => {
        const filename = path.resolve(project, 'svelte.config.js');

        if (!fs.existsSync(filename)) {
            console.warn(`Missing ${filename} — skipping`);
            return;
        }

        // skipped
    });

// node_modules/vite/dist/node/cli.js

cli
    // skipped
    // Vite CLI requires to document new option
    .option('--project <path>', '[string] Project root');

Importance

would make my life easier

Additional Information

No response

weirdo-neutrino commented 1 year ago

7444

weirdo-neutrino commented 1 year ago

Here is a smaller approach. It prints some warnings which can be solved, but I'm not going to polish it more. It does not require new flag, but requires new svelte config field.

// package.json

{
    "scripts": {
        "f:sync":     "svelte-kit sync",
        "f:dev":      "npm run f:sync && vite dev     --config frontend/vite.config.js",
        "f:build":    "npm run f:sync && vite build   --config frontend/vite.config.js",
        "f:preview":  "                  vite preview --config frontend/vite.config.js"
    },
    "workspaces": [
        "frontend"
    ]
}
// frontend/package.json

{
    "devDependencies": {
        "@sveltejs/kit": "^1.3.6"
    }
}
// frontend/svelte.config.js

import adapter from '@sveltejs/adapter-node'

export default {
    kit: {
        adapter: adapter({
            out: 'build/frontend'
        }),
        files: {
            assets:         'frontend/static',
            hooks: {
                client:     'frontend/hooks.client',
                server:     'frontend/hooks.server',
            },
            lib:            'frontend/src/lib',
            params:         'frontend/src/params',
            routes:         'frontend/src/routes',
            serviceWorker:  'frontend/src/service-worker',
            appTemplate:    'frontend/src/app.html',
            errorTemplate:  'frontend/src/error.html'
        },
        outDir: 'build/.cache/frontend',
        project: 'frontend'
    }
}
// frontend/tsconfig.json

{
    "extends": "../../build/.cache/frontend/tsconfig.json"
}
// node_modules/@sveltejs/kit/src/core/config/index.js

export async function load_config({ cwd = process.cwd() } = {}) {
    const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
    let config_file = undefined;
    if (pkg.workspaces) {
        const packages = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
        for (const directory of packages) {
            if (config_file !== undefined) break

            for (const dir of glob(directory, { cwd })) {
                const d = path.resolve(cwd, dir);

                const p = path.resolve(d, 'package.json')
                if (!fs.existsSync(p)) continue;

                const s = path.resolve(d, 'svelte.config.js')
                if (!fs.existsSync(s)) continue;

                const lpkg = JSON.parse(fs.readFileSync(p, 'utf8'));
                if (!lpkg.dependencies?.['@sveltejs/kit'] && !lpkg.devDependencies?.['@sveltejs/kit']) continue;

                config_file = s;
                break;
            }
        }
    }

    if (config_file === undefined) {
        return process_config({}, { cwd });
    }

    const config = await import(`${url.pathToFileURL(config_file).href}?ts=${Date.now()}`);

    return process_config(config.default, { cwd });
}
// node_modules/@sveltejs/kit/src/core/config/options.js

project: string(process.cwd())
// node_modules/@sveltejs/kit/src/core/sync/write_tsconfig.js

export function write_tsconfig(kit) {
    const out = path.join(kit.outDir, 'tsconfig.json');

    const user_config = load_user_tsconfig(kit.project);
    if (user_config) validate_user_config(kit, kit.project, out, user_config);
dummdidumm commented 1 year ago

Could you please in a few short sentences (without much code) explain what exactly you want?

weirdo-neutrino commented 1 year ago

To move svelte.config.js and its tsconfig.json outside of root directory from where I can mange it as a subproject.

root |- project_1 |- package_2 |- sveltekit_directory_3 |- build_scripts_4

As an alternative you'll have to use some big tools like https://turbo.build/repo or https://nx.dev/ or just use it standalone. It will be nice to have something in the middle with simple flags or config fields to direct whole tooling to subproject directory.

Rich-Harris commented 1 year ago

Why is it important to be able to run projects from outside the project directory? I don't understand at all what you're trying to do.

weirdo-neutrino commented 1 year ago

Okay. Look. I chose "would make my life easier" option at "Importance". It's not like I cannot call vite through --workspace. Or do some cd-in/cd-out. There are always ways around in software anyway. If someone will need it, there will be this issue with code above. Let's get pass this part.

Typescript.

I linked another enhancement issue 7444. It's only a matter of specifying new option field in kit object at svelte.config.js by modifying options.js and and loading it in write_tsconfig.js instead of cwd as far as I understood the code.

So the question is: Is it feasible to add another field to kit configuration in some future or I'm misunderstanding code/design?

Rich-Harris commented 1 year ago

The question isn't whether it's feasible, it's whether the benefits outweigh the drawbacks. I don't think they do. Having a predictable location for these files is beneficial for other tooling (such as editor integrations); we'd be losing that and adding API surface area.

would make my life easier

I'm not sure it does! This...

"scripts": {
  "f:sync":    "cd frontend && npm run sync",
  "f:dev":     "cd frontend && npm run dev"
  "f:build":   "cd frontend && npm run build",
  "f:preview": "cd frontend && npm run preview"
}

...is clearly easier than this:

"scripts": {
  "f:sync":    "svelte-kit sync                --project frontend",
  "f:dev":     "npm run f:sync && vite dev     --project frontend --config frontend/vite.config.js",
  "f:build":   "npm run f:sync && vite build   --project frontend --config frontend/vite.config.js",
  "f:preview": "                  vite preview --project frontend --config frontend/vite.config.js"
}

As for tsconfig.json, you could easily have a tsconfig.config.json for typechecking your config files separately, if that's truly necessary.

weirdo-neutrino commented 1 year ago

So I miss understood design.

And considering "scripts" It will be easier to fire npm run dev -w frontent than having this facade of vague and highly conflicting/clashing names.

I think this issue can be closed.