UNPKG

silki

Version:

Cli tool for build react app, based on generator-react-multipage, create-react-app, roadhog. Support react multiple pages app develop.

338 lines (302 loc) 10.1 kB
import fs from 'fs'; import spawn from 'cross-spawn'; import detect from 'detect-port'; import clearConsole from 'react-dev-utils/clearConsole'; import getProcessForPort from 'react-dev-utils/getProcessForPort'; import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages'; import openBrowser from 'react-dev-utils/openBrowser'; import prompt from 'react-dev-utils/prompt'; import webpack from 'webpack'; import historyApiFallback from 'connect-history-api-fallback'; import WebpackDevServer from 'webpack-dev-server'; import chalk from 'chalk'; import chokidar from 'chokidar'; import getPaths from './utils/paths'; import getConfig from './utils/getConfig'; import applyWebpackConfig, { warnIfExists } from './utils/applyWebpackConfig'; import { applyMock, outputError as outputMockError } from './utils/mock'; import glob from 'glob'; import babelCompile from './babel'; process.env.NODE_ENV = 'development'; let DEFAULT_PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 8000; const isInteractive = process.stdout.isTTY; const cwd = process.cwd(); const paths = getPaths(cwd); let compiler; const argv = require('yargs') .usage('Usage: silki server [options]') .option('open', { type: 'boolean', describe: 'Open url in browser after started', default: true, }) .option('mod', { type: 'boolean', alias: 'm', describe: 'create youku mod server', default: false, }) .help('h') .argv; let rcConfig; let config; let entries; // 页面入口,当页面入口发生变化时(新建页面时),重启服务器 let startTime; // 计算编译时间 function clearConsoleWrapped() { if (process.env.CLEAR_CONSOLE !== 'NONE') { clearConsole(); } } function readRcConfig() { try { rcConfig = getConfig(process.env.NODE_ENV, cwd); } catch (e) { console.log(chalk.red('Failed to parse .silkrc config.')); console.log(); console.log(e.message); process.exit(1); } } function readWebpackConfig() { if(argv.mod) { config = applyWebpackConfig( require('./config/webpack.config.mod.dev')(rcConfig, cwd), process.env.NODE_ENV, ); } else { config = applyWebpackConfig( require('./config/webpack.config.dev')(rcConfig, cwd), process.env.NODE_ENV, ); } } function setupCompiler(host, port, protocol) { compiler = webpack(config); // console.log('silk的webpack配置为===='+JSON.stringify(config)); compiler.plugin('invalid', () => { if (isInteractive) { clearConsoleWrapped(); } console.log('Compiling...'); startTime = (new Date()).getTime(); }); let isFirstCompile = true; compiler.plugin('done', (stats) => { if (isInteractive) { clearConsoleWrapped(); } const messages = formatWebpackMessages(stats.toJson({}, true)); const isSuccessful = !messages.errors.length && !messages.warnings.length; const showInstructions = isSuccessful && (isInteractive || isFirstCompile); warnIfExists(); if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); // show compile time console.log(chalk.yellow('Compile time: ' + ((new Date()).getTime() - startTime) / 1000 + 's')); } if (showInstructions) { console.log(); console.log('The app is running at:'); console.log(); console.log(` ${chalk.cyan(`${protocol}://${host}:${port}/`)}`); console.log(); console.log('Note that the development build is not optimized.'); console.log(`To create a production build, use ${chalk.cyan('npm run build')}.`); console.log(); isFirstCompile = false; } // If errors exist, only show errors. if (messages.errors.length) { console.log(chalk.red('Failed to compile.')); console.log(); messages.errors.forEach((message) => { console.log(message); console.log(); }); // Show warnings if no errors were found. } else if (messages.warnings.length) { console.log(chalk.yellow('Compiled with warnings.')); console.log(); messages.warnings.forEach((message) => { console.log(message); console.log(); }); // Teach some ESLint tricks. console.log('You may use special comments to disable some warnings.'); console.log(`Use ${chalk.yellow('// eslint-disable-next-line')} to ignore the next line.`); console.log(`Use ${chalk.yellow('/* eslint-disable */')} to ignore all warnings in a file.`); console.log(); } if (isInteractive) { outputMockError(); } }); } function addMiddleware(devServer) { const proxy = require(paths.appPackageJson).proxy; devServer.use(historyApiFallback({ disableDotRule: true, htmlAcceptHeaders: proxy ? ['text/html'] : ['text/html', '*/*'], })); // TODO: proxy index.html, ... devServer.use(devServer.middleware) ; } function runDevServer(host, port, protocol) { const devServer = new WebpackDevServer(compiler, { disableHostCheck: true, // fix invalid host error compress: true, clientLogLevel: 'none', contentBase: paths.appPublic, hot: true, publicPath: config.output.publicPath, quiet: true, watchOptions: { ignored: /node_modules/, }, https: protocol === 'https', host, proxy: rcConfig.proxy || {}, headers: { "Access-Control-Allow-Origin": "*" } }); addMiddleware(devServer); applyMock(devServer); if (isInteractive) { clearConsoleWrapped(); } console.log(chalk.cyan('Starting the development server...')); devServer.listen(port, '0.0.0.0', (err) => { if (err) { return console.log(err); } process.send('READY'); if (isInteractive) { clearConsoleWrapped(); } console.log(chalk.cyan('Starting the development server...')); console.log(); if (isInteractive) { outputMockError(); } if (argv.open) { openBrowser(`${protocol}://${host}:${port}/`); } }); setupWatch(devServer, port); } function setupWatch(devServer) { const files = [ paths.resolveApp('.silkrc'), paths.resolveApp('.silkrc.js'), paths.resolveApp('webpack.config.js'), ] .concat(typeof rcConfig.theme === 'string' ? paths.resolveApp(rcConfig.theme) : []); const watcher = chokidar.watch(files, { ignored: /node_modules/, persistent: true, }); watcher.on('change', (path) => { console.log(chalk.green(`File ${path.replace(paths.appDirectory, '.')} changed, try to restart server`)); watcher.close(); devServer.close(); process.send('RESTART'); }); /** * 监听是否有新建页面,(是否有新的页面入口) * 如果有新建页面,则重启服务器 */ let globPath = rcConfig.entry ? paths.appDirectory + '/' + rcConfig.entry : `${paths.appSrc}/pages/*/index.js`; entries = glob.sync(globPath); const pageDir = paths.resolveApp('./src/pages/'); chokidar.watch(pageDir).on('add', (path) => { let curEntries = glob.sync(globPath); let diff = false; let len = curEntries.length; if (len != entries.length) { diff = true; } else { for (let i = 0; i < len; i++) { if (curEntries[i] != entries[i]) { diff = true; break; } } } if (diff) { console.log(chalk.green(`Added ${path.replace(paths.appDirectory, '.')} , try to restart server`)); watcher.close(); devServer.close(); process.send('RESTART'); entries = curEntries; } }); } // watch 需要 babel 直接编译的文件 function watchBabel() { let globPath = `${paths.appSrc}/workers/**/*.js`; const babelCmd = rcConfig.babelCmd; let watchDir = babelCmd ? babelCmd.split(/\s+/)[1] : 'src/workers'; if (babelCmd) { globPath = `${watchDir}/**/*.js`; } entries = glob.sync(globPath); watchDir = paths.resolveApp(watchDir); const watcher = chokidar.watch(watchDir).on('change', (path) => { console.log(chalk.green(`Changed ${path.replace(paths.appDirectory, '.')} , try to recompile`)); babelCompile(rcConfig); }); } function run(port) { startTime = (new Date()).getTime(); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const host = process.env.HOST || 'localhost'; setupCompiler(host, port, protocol); runDevServer(host, port, protocol); } function init() { readRcConfig(); // 如果配置了dll功能,首先看是否存在了 vendor.dll.js 文件,如果不存在,则需要先运行 silk dll if (rcConfig.dll) { if (!fs.existsSync(`${paths.appPublic}/vendor.dll.js`)) { console.log(' ️⚠️ ️You are using webpack dll. '); console.log('Please run ' + chalk.yellow('silk dll') + ' first'); console.log('and add code ' + chalk.green('<script src="/vendor.dll.js"></script>') + ' to your template-dev.html '); return; } } readWebpackConfig(); // 处理需要直接用 babel 编译的文件,无需 webpack 处理 if (rcConfig.useBabelCmd) { babelCompile(rcConfig); watchBabel(); } // 命令行设置端口优先级最高,用户配置优先级次之,默认端口8000 DEFAULT_PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : rcConfig.port; detect(DEFAULT_PORT).then((port) => { // 当前端口未被占用 if (port == DEFAULT_PORT) { run(port); return; } // 当前端口被占用时,分配一个新的端口 port if (isInteractive) { clearConsoleWrapped(); const existingProcess = getProcessForPort(DEFAULT_PORT); const question = chalk.yellow(`Something is already running on port ${DEFAULT_PORT}.${((existingProcess) ? ` Probably:\n ${existingProcess}` : '')}\n\nWould you like to run the app on another port instead?`); prompt(question, true).then((shouldChangePort) => { if (shouldChangePort) { run(port); } }); } else { console.log(chalk.red(`Something is already running on port ${DEFAULT_PORT}.`)); } }); } init();