Skip to content

Commit

Permalink
feat: add cwd options
Browse files Browse the repository at this point in the history
  • Loading branch information
liximomo committed Jul 1, 2020
1 parent 2427728 commit 7839e2e
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 88 deletions.
6 changes: 1 addition & 5 deletions packages/shuvi/src/api/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { PluginApi } from '../pluginApi';
import { resolvePlugin } from './utils';
import { IApiConfig, IPaths } from '@shuvi/types';
import path from 'path';
import { CONFIG_FILE } from '@shuvi/shared/lib/constants';

describe('api', () => {
let gApi: Api;
Expand Down Expand Up @@ -93,10 +92,7 @@ describe('api', () => {
expect(process.env.READ_ENV).toBeUndefined();

await getApi({
config: {
rootDir: path.join(__dirname, 'fixtures', 'dotenv')
},
configFile: path.join(__dirname, 'fixtures', 'dotenv', CONFIG_FILE)
cwd: path.join(__dirname, 'fixtures', 'dotenv')
});

expect(process.env.READ_ENV).toBe('true');
Expand Down
15 changes: 12 additions & 3 deletions packages/shuvi/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path';
import {
IApiConfig,
IApi,
Expand Down Expand Up @@ -26,12 +27,14 @@ import { getPaths } from './paths';
const ServiceModes: IShuviMode[] = ['development', 'production'];

interface IApiOPtions {
cwd?: string;
mode?: IShuviMode;
config?: IConfig;
configFile?: string;
}

class Api extends Hookable implements IApi {
private _cwd: string;
private _mode: IShuviMode;
private _userConfig?: IConfig;
private _config!: IApiConfig;
Expand All @@ -44,7 +47,7 @@ class Api extends Hookable implements IApi {
private _plugins!: IPlugin[];
private _pluginApi!: PluginApi;

constructor({ mode, config, configFile }: IApiOPtions) {
constructor({ cwd, mode, config, configFile }: IApiOPtions) {
super();
if (mode) {
this._mode = mode;
Expand All @@ -54,6 +57,7 @@ class Api extends Hookable implements IApi {
this._mode = 'production';
}

this._cwd = path.resolve(cwd || '.');
this._configFile = configFile;
this._userConfig = config;
}
Expand All @@ -73,7 +77,11 @@ class Api extends Hookable implements IApi {
async init() {
this._app = new App();

const configFromFile = await loadConfig(this._configFile, this._userConfig);
const configFromFile = await loadConfig({
rootDir: this._cwd,
configFile: this._configFile,
overrides: this._userConfig
});

const config: IApiConfig = deepmerge(defaultConfig, configFromFile);

Expand Down Expand Up @@ -311,11 +319,12 @@ class Api extends Hookable implements IApi {
export type { Api };

export async function getApi({
cwd,
mode,
config,
configFile
}: IApiOPtions): Promise<Api> {
const api = new Api({ mode, config, configFile });
const api = new Api({ cwd, mode, config, configFile });

await api.init();
return api;
Expand Down
10 changes: 5 additions & 5 deletions packages/shuvi/src/cli/apis/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import { BUILD_CLIENT_DIR } from '../../constants';
import { renderToHTML } from '../../lib/renderToHTML';

export interface IBuildOptions {
cwd?: string;
config?: IConfig;
configFile?: string;
target?: 'spa' | 'ssr';
target: 'spa' | 'ssr';
}

const defaultBuildOptions = {
target: 'ssr',
config: {},
configFile: ''
} as const;

async function bundle({ api }: { api: Api }) {
Expand Down Expand Up @@ -77,12 +76,13 @@ async function buildHtml({
}
}

export async function build(options: IBuildOptions) {
const opts: Required<IBuildOptions> = {
export async function build(options: Partial<IBuildOptions>) {
const opts: IBuildOptions = {
...defaultBuildOptions,
...options
};
const api = await getApi({
cwd: opts.cwd,
mode: 'production',
config: opts.config,
configFile: opts.configFile
Expand Down
23 changes: 14 additions & 9 deletions packages/shuvi/src/cli/cmds/build.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import program from 'commander';
import path from 'path';
import { IConfig } from '../../config';
import { build } from '../apis/build';
//@ts-ignore
import pkgInfo from '../../../package.json';
import { getProjectDir } from '../utils';
import { CONFIG_FILE } from '@shuvi/shared/lib/constants';

interface CliOptions {
interface CLIParams {
publicPath?: string;
target?: 'spa';
}
Expand All @@ -32,7 +30,8 @@ function set(obj: any, path: string, value: any) {
obj[final] = value;
}

function applyCliOptions(cliOptions: Record<string, any>, config: IConfig) {
function getConfigFromCli(cliOptions: Record<string, any>) {
const config = {};
Object.keys(CliConfigMap).forEach(key => {
if (typeof program[key] !== 'undefined') {
const value = CliConfigMap[key];
Expand All @@ -43,13 +42,15 @@ function applyCliOptions(cliOptions: Record<string, any>, config: IConfig) {
}
}
});
return config;
}

export default async function main(argv: string[]) {
program
.name(pkgInfo.name)
.usage(`build [dir] [options]`)
.helpOption()
.option('--config <file>', 'path to config file')
.option(
'--public-path <url>',
'specify the asset prefix. eg: https://some.cdn.com'
Expand All @@ -61,13 +62,17 @@ export default async function main(argv: string[]) {
)
.parse(argv, { from: 'user' });

const configFile = path.join(getProjectDir(program), CONFIG_FILE);
const cliOpts = program as CliOptions;
const config = {};
applyCliOptions(cliOpts, config);
const cwd = getProjectDir(program);
const cliParams = program as CLIParams;
const config = getConfigFromCli(cliParams);

try {
await build({ config, target: cliOpts.target, configFile });
await build({
cwd,
config,
configFile: program.config && path.resolve(cwd, program.config),
target: cliParams.target
});
console.log('Build successfully!');
} catch (error) {
console.error('Failed to build.\n');
Expand Down
10 changes: 7 additions & 3 deletions packages/shuvi/src/cli/cmds/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@ import { shuvi } from '../../shuvi';
//@ts-ignore
import pkgInfo from '../../../package.json';
import { getProjectDir } from '../utils';
import { CONFIG_FILE } from '@shuvi/shared/lib/constants';

export default async function main(argv: string[]) {
program
.name(pkgInfo.name)
.usage(`dev [dir] [options]`)
.helpOption()
.option('--config <file>', 'path to config file')
.option('--host <host>', 'specify host')
.option('--port <port>', 'specify port')
.parse(argv, { from: 'user' });

const configFile = path.join(getProjectDir(program), CONFIG_FILE);
const cwd = getProjectDir(program);
const port = program.port || 3000;
const host = program.host || 'localhost';

const shuviApp = shuvi({ dev: true, configFile });
const shuviApp = shuvi({
dev: true,
cwd,
configFile: program.config && path.resolve(cwd, program.config)
});

try {
console.log('Starting the development server...');
Expand Down
9 changes: 6 additions & 3 deletions packages/shuvi/src/cli/cmds/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ import { shuvi } from '../../shuvi';
//@ts-ignore
import pkgInfo from '../../../package.json';
import { getProjectDir } from '../utils';
import { CONFIG_FILE } from '@shuvi/shared/lib/constants';

export default async function main(argv: string[]) {
program
.name(pkgInfo.name)
.usage(`serve [dir] [options]`)
.helpOption()
.option('--config <file>', 'path to config file')
.option('--host <host>', 'specify host')
.option('--port <port>', 'specify port')
.parse(argv, { from: 'user' });

const configFile = path.join(getProjectDir(program), CONFIG_FILE);
const cwd = getProjectDir(program);
const port = program.port || 3000;
const host = program.host || 'localhost';

const shuviApp = shuvi({ configFile });
const shuviApp = shuvi({
cwd,
configFile: program.config && path.resolve(cwd, program.config)
});
try {
await shuviApp.listen(port, host);
console.log(`Ready on http://${host}:${port}`);
Expand Down
12 changes: 9 additions & 3 deletions packages/shuvi/src/config/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('config', () => {
});

test('should use process.cwd for rootDir when rootDir is not specified', async () => {
const config = await loadConfig(undefined, {});
const config = await loadConfig();

expect(config.rootDir).toBe(process.cwd());
});
Expand All @@ -25,16 +25,22 @@ describe('config', () => {
expect(config.ssr).toBe(false);
});

test('should not load config when userConfig is given and configFile is undefined', async () => {
test('should warn when customed configFile does not exist', async () => {
const warn = jest
.spyOn(global.console, 'warn')
.mockImplementation(() => null);
const config = await loadFixture(
'base',
{
ssr: false,
publicDir: './'
},
false
'none-exist-file'
);

expect(warn).toHaveBeenCalledWith(
expect.stringMatching(/Config file not found:/)
);
expect(config.ssr).toBe(false);
expect(config.publicDir).toBe('./');
});
Expand Down
13 changes: 6 additions & 7 deletions packages/shuvi/src/config/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path';
import { CONFIG_FILE } from '@shuvi/shared/lib/constants';
import { IConfig } from '..';
import { loadConfig } from '../index';

Expand All @@ -10,11 +9,11 @@ export function resolveFixture(name: string) {
export async function loadFixture(
name: string,
userConfig: IConfig = {},
configFile: boolean = true
configFile?: string
) {
userConfig.rootDir = resolveFixture(name);
return loadConfig(
configFile ? path.join(resolveFixture(name), CONFIG_FILE) : undefined,
userConfig
);
return loadConfig({
rootDir: resolveFixture(name),
overrides: userConfig,
configFile
});
}
53 changes: 23 additions & 30 deletions packages/shuvi/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { IApiConfig } from '@shuvi/types';
import path from 'path';
import { PUBLIC_PATH } from '../constants';
import { PUBLIC_PATH, CONFIG_FILE } from '../constants';
import { loadDotenvConfig } from './loadDotenvConfig';
import { deepmerge } from '@shuvi/utils/lib/deepmerge';

export type IConfig = Partial<IApiConfig>;

export interface LoadConfigOptions {
rootDir?: string;
configFile?: string;
overrides?: IConfig;
}

export const defaultConfig: IApiConfig = {
ssr: true,
env: {},
Expand All @@ -18,40 +24,27 @@ export const defaultConfig: IApiConfig = {
}
};

async function loadConfigFromFile<T>(configPath: string): Promise<T> {
const absolutePath = path.isAbsolute(configPath)
? configPath
: path.resolve(configPath);
let config = {} as T;
export async function loadConfig({
rootDir = '.',
configFile = CONFIG_FILE,
overrides = {}
}: LoadConfigOptions = {}): Promise<IConfig> {
rootDir = path.resolve(rootDir);
configFile = path.resolve(rootDir, configFile);

// read dotenv so we can get env in shuvi.config.js
loadDotenvConfig(rootDir);

let fileConfig: IConfig = {};
try {
config = require(absolutePath);
config = (config as any).default || config;
fileConfig = require(configFile);
fileConfig = (fileConfig as any).default || fileConfig;
} catch (err) {
// Ignore MODULE_NOT_FOUND
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
} else if (configFile !== path.resolve(rootDir, CONFIG_FILE)) {
console.warn('Config file not found: ' + configFile);
}
}

return config;
}

export async function loadConfig(
configFile?: string,
userConfig: IConfig = {}
): Promise<IConfig> {
const rootDir = userConfig.rootDir
? path.resolve(userConfig.rootDir)
: process.cwd();

// read dotenv so we can get env in shuvi.config.js
loadDotenvConfig(rootDir);

if (configFile) {
const config = await loadConfigFromFile<IConfig>(configFile);
return deepmerge({ rootDir }, config, userConfig);
}

return deepmerge({ rootDir }, userConfig);
return deepmerge({ rootDir }, fileConfig, overrides);
}
2 changes: 1 addition & 1 deletion packages/shuvi/src/lib/sendHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function sendHTML(
res: ServerResponse,
html: string
) {
if (res.finished || res.headersSent) return;
if (res.writableEnded || res.headersSent) return;

if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
Expand Down
Loading

0 comments on commit 7839e2e

Please sign in to comment.