diff --git a/packages/Maleo.js/server.js b/packages/Maleo.js/server.js index dc01d12b..58b1ea79 100644 --- a/packages/Maleo.js/server.js +++ b/packages/Maleo.js/server.js @@ -1 +1,5 @@ -module.exports = require('./lib/server/server.js'); +if (process.env.NODE_ENV === 'development') { + module.exports = require('./lib/server/dev-server.js'); +} else { + module.exports = require('./lib/server/server'); +} diff --git a/packages/Maleo.js/src/bin/maleo.ts b/packages/Maleo.js/src/bin/maleo.ts index 235bafdb..98091b27 100755 --- a/packages/Maleo.js/src/bin/maleo.ts +++ b/packages/Maleo.js/src/bin/maleo.ts @@ -73,6 +73,7 @@ if (type === 'run') { env, buildType: 'server', callback: exec, + minimalBuild: true, }); } }); diff --git a/packages/Maleo.js/src/build/index.ts b/packages/Maleo.js/src/build/index.ts index c7541dfc..4e705f33 100644 --- a/packages/Maleo.js/src/build/index.ts +++ b/packages/Maleo.js/src/build/index.ts @@ -8,20 +8,23 @@ import { IBuildOptions } from '@interfaces/build/IBuildOptions'; import { Context, CustomConfig, StaticPages } from '@interfaces/build/IWebpackInterfaces'; import { buildStatic } from '@build/static/static'; -const cwd = process.cwd(); - export const getConfigs = (options: IBuildOptions): Configuration[] => { - const { env, buildType } = options; + const { env, buildType, minimalBuild } = options; const context: Context = { env, - projectDir: cwd, + projectDir: process.cwd(), }; - const userConfig: CustomConfig = loadUserConfig(cwd); - - const clientConfig = createWebpackConfig({ isServer: false, ...context }, userConfig); - const serverConfig = createWebpackConfig({ isServer: true, ...context }, userConfig); + const userConfig: CustomConfig = loadUserConfig(context.projectDir); + const clientConfig = createWebpackConfig( + { isServer: false, ...context, minimalBuild }, + userConfig, + ); + const serverConfig = createWebpackConfig( + { isServer: true, ...context, minimalBuild }, + userConfig, + ); if (buildType === 'server') { return [serverConfig]; @@ -43,7 +46,7 @@ export const exportStatic = (userConfig: CustomConfig) => { const staticPages: StaticPages = userConfig.staticPages; try { console.log('[STATIC] Starting to export static pages'); - buildStatic(staticPages, cwd); + buildStatic(staticPages, process.cwd()); } catch (error) { console.log('[STATIC] Error when tried to export static pages, error:', error); } diff --git a/packages/Maleo.js/src/build/webpack/plugins/stats-writer.ts b/packages/Maleo.js/src/build/webpack/plugins/stats-writer.ts index be679c1a..dea8eaf4 100644 --- a/packages/Maleo.js/src/build/webpack/plugins/stats-writer.ts +++ b/packages/Maleo.js/src/build/webpack/plugins/stats-writer.ts @@ -33,10 +33,7 @@ export class StatsWriterPlugin { // Extract dynamic assets to dynamic key const key = 'assetsByChunkName'; - stats = { - static: this.extractDynamic(stats[key], false), - dynamic: this.extractDynamic(stats[key], true), - }; + stats = mapStats(stats, key); // Transform to string const [err, statsStr] = await to( @@ -68,10 +65,27 @@ export class StatsWriterPlugin { return void callback(); } }; +} + +export function to( + promise: Promise, + errorExt?: object, +): Promise<[U | null, T | undefined]> { + return promise + .then<[null, T]>((data: T) => [null, data]) + .catch<[U, undefined]>((err: U) => { + if (errorExt) { + Object.assign(err, errorExt); + } + + return [err, undefined]; + }); +} +export const mapStats = (stats: any, key: string) => { // Extract dynamic assets - extractDynamic = (stats, isDynamic = false) => { - return Object.keys(stats) + const extractDynamic = (stat, isDynamic = false) => { + return Object.keys(stat) .filter((k) => { const regex = /dynamic\./; if (isDynamic) { @@ -82,24 +96,14 @@ export class StatsWriterPlugin { .reduce( (p, c) => ({ ...p, - [c]: stats[c], + [c]: stat[c], }), {}, ); }; -} - -export function to( - promise: Promise, - errorExt?: object, -): Promise<[U | null, T | undefined]> { - return promise - .then<[null, T]>((data: T) => [null, data]) - .catch<[U, undefined]>((err: U) => { - if (errorExt) { - Object.assign(err, errorExt); - } - return [err, undefined]; - }); -} + return { + static: extractDynamic(stats[key], false), + dynamic: extractDynamic(stats[key], true), + }; +}; diff --git a/packages/Maleo.js/src/build/webpack/webpack.ts b/packages/Maleo.js/src/build/webpack/webpack.ts index 44e20e31..c2aa608b 100644 --- a/packages/Maleo.js/src/build/webpack/webpack.ts +++ b/packages/Maleo.js/src/build/webpack/webpack.ts @@ -65,7 +65,7 @@ const defaultUserConfig: CustomConfig = { }; export const createWebpackConfig = (context: Context, customConfig: CustomConfig) => { - const { env, isServer } = context; + const { env, isServer, minimalBuild } = context; const { cache, buildDir, @@ -96,6 +96,7 @@ export const createWebpackConfig = (context: Context, customConfig: CustomConfig analyzeBundle, buildDirectory, name, + minimalBuild, }; const [entry, optimization, rules, plugins, output] = [ @@ -207,7 +208,7 @@ export const getDefaultEntry = ( context: BuildContext, customConfig: CustomConfig, ): Configuration['entry'] => { - const { isServer, projectDir, isDev } = context; + const { isServer, projectDir, isDev, minimalBuild } = context; const { routes, document, wrap, app } = getStaticEntries(context, customConfig); @@ -217,13 +218,22 @@ export const getDefaultEntry = ( ? path.join(projectDir, SERVER_ENTRY_NAME) : path.resolve(__dirname, '../../../lib/default/_server.js'); - return { + const serverEntries = { server: [isDev && 'webpack/hot/signal', serverEntry].filter(Boolean) as string[], routes, document, wrap, app, }; + + if (minimalBuild) { + console.log('[Webpack] Running minimal server build'); + return { + server: serverEntries.server, + }; + } + + return serverEntries; } const customClientExist = fileExist(projectDir, path.join(projectDir, 'client')); @@ -263,25 +273,25 @@ export const getDefaultOptimizations = ( let clientOptimizations: Configuration['optimization']; clientOptimizations = { ...commonOptimizations, - - runtimeChunk: { - name: RUNTIME_CHUNK_FILE, - }, + runtimeChunk: { name: RUNTIME_CHUNK_FILE }, splitChunks: { - chunks: 'all', + chunks: 'async', + minSize: 30000, + maxSize: 0, + minChunks: 1, + maxAsyncRequests: 5, + maxInitialRequests: 3, + name: false, cacheGroups: { - default: false, vendors: { + name: 'vendors', test: /[\\/]node_modules[\\/]/, - // test: /node_modules\/(?!((.*)webpack(.*))).*/, - name(module) { - // get the name. E.g. node_modules/packageName/not/this/part.js - // or node_modules/packageName - const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; - - // npm package names are URL-safe, but some servers don't like @ symbols - return `npm.${packageName.replace('@', '')}`; - }, + priority: -10, + }, + default: { + minChunks: 2, + priority: -20, + reuseExistingChunk: true, }, }, }, @@ -290,7 +300,38 @@ export const getDefaultOptimizations = ( if (!isDev) { clientOptimizations = { ...clientOptimizations, - + splitChunks: { + chunks: 'all', + name: false, + minSize: 30000, + maxSize: 0, + cacheGroups: { + default: false, + vendors: false, + react: { + name: 'react', + chunks: 'all', + test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, + }, + commons: { name: 'commons', chunks: 'all', minChunks: 2 }, + }, + }, + // chunks: 'async', // splitChunks: { + // cacheGroups: { + // default: false, + // vendors: { + // test: /[\\/]node_modules[\\/]/, + // name(module) { + // // get the name. E.g. node_modules/packageName/not/this/part.js + // // or node_modules/packageName + // const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; + + // // npm package names are URL-safe, but some servers don't like @ symbols + // return `npm.${packageName.replace('@', '')}`; + // }, + // }, + // }, + // }, minimize: true, minimizer: [ new TerserPlugin({ @@ -314,25 +355,6 @@ export const getDefaultOptimizations = ( }, }), ], - - splitChunks: { - chunks: 'all', - cacheGroups: { - default: false, - vendors: false, - - commons: { - name: 'commons', - chunks: 'all', - minChunks: 2, - }, - react: { - name: 'commons', - chunks: 'all', - test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, - }, - }, - }, }; } diff --git a/packages/Maleo.js/src/default/_server.ts b/packages/Maleo.js/src/default/_server.ts index f5b6dcf3..01f179ae 100644 --- a/packages/Maleo.js/src/default/_server.ts +++ b/packages/Maleo.js/src/default/_server.ts @@ -1,8 +1,14 @@ import { Server } from '~/src/server/server'; +let SelectedServer = Server; +// No need to load DevServer code for production server +if (__DEV__) { + SelectedServer = require('~/src/server/dev-server').default; +} + const PORT = process.env.PORT || 3000; -const defaultServer = Server.init({ +const defaultServer = SelectedServer.init({ port: PORT, }); diff --git a/packages/Maleo.js/src/interfaces/build/IBuildOptions.ts b/packages/Maleo.js/src/interfaces/build/IBuildOptions.ts index 6e52bb84..3e085357 100644 --- a/packages/Maleo.js/src/interfaces/build/IBuildOptions.ts +++ b/packages/Maleo.js/src/interfaces/build/IBuildOptions.ts @@ -3,5 +3,6 @@ import webpack from 'webpack'; export interface IBuildOptions { env: 'development' | 'production' | 'none'; buildType: 'server' | 'client' | 'all'; + minimalBuild?: boolean; callback?: (err: Error, stats: webpack.Stats) => void; } diff --git a/packages/Maleo.js/src/interfaces/build/IWebpackInterfaces.ts b/packages/Maleo.js/src/interfaces/build/IWebpackInterfaces.ts index 9bf2d838..1259addc 100644 --- a/packages/Maleo.js/src/interfaces/build/IWebpackInterfaces.ts +++ b/packages/Maleo.js/src/interfaces/build/IWebpackInterfaces.ts @@ -4,6 +4,7 @@ export interface Context { isServer?: boolean; env: 'development' | 'production' | 'none'; projectDir: string; + minimalBuild?: boolean; } export interface CustomConfig { diff --git a/packages/Maleo.js/src/interfaces/render/IRender.ts b/packages/Maleo.js/src/interfaces/render/IRender.ts index 5e865010..69c9bb27 100644 --- a/packages/Maleo.js/src/interfaces/render/IRender.ts +++ b/packages/Maleo.js/src/interfaces/render/IRender.ts @@ -129,6 +129,17 @@ export interface RenderParam { renderPage?: ( param: RenderPageParams, ) => (fn?: ModPageFn) => Promise<{ html: string; bundles: LoadableBundles[] }>; + + preloadScripts: ( + dir: string, + tempArray: any[], + context: PreloadScriptContext, + ) => any[] | Promise; +} + +export interface PreloadScriptContext { + req: Request; + res: Response; } export interface RenderPageParams { diff --git a/packages/Maleo.js/src/interfaces/server/IOptions.ts b/packages/Maleo.js/src/interfaces/server/IOptions.ts index b54ca86a..68768259 100644 --- a/packages/Maleo.js/src/interfaces/server/IOptions.ts +++ b/packages/Maleo.js/src/interfaces/server/IOptions.ts @@ -1,12 +1,4 @@ -import { DocumentProps, AppProps } from '../render/IRender'; - export interface IOptions { port: number | string; - assetDir?: string; - routes?: []; - - _document?: React.ReactElement; - _app?: React.ReactElement; - _wrap?: React.ReactElement; } diff --git a/packages/Maleo.js/src/server/dev-server.ts b/packages/Maleo.js/src/server/dev-server.ts new file mode 100644 index 00000000..e1bfca01 --- /dev/null +++ b/packages/Maleo.js/src/server/dev-server.ts @@ -0,0 +1,125 @@ +import { Request, Response } from 'express'; +import path from 'path'; +import devMiddleware from 'webpack-dev-middleware'; +import hotMiddleware from 'webpack-hot-middleware'; + +import { Server } from './server'; +import { requireRuntime } from '@utils/require'; +import { IOptions } from '../interfaces/server/IOptions'; +import { render } from './render'; +import { RenderParam } from '../interfaces/render/IRender'; +import { mapAssets } from './extract-stats'; +import { mapStats } from '@build/webpack/plugins/stats-writer'; +// import { LazyBuild } from './lazy-development-build'; + +const ignored = [/\.git/, /\.maleo\//, /node_modules/]; + +class DevServer extends Server { + static init = (options: IOptions) => { + return new DevServer(options); + }; + + memoryStats = null; + wdm; + + constructor(options: IOptions) { + super(options); + + this.setupDevelopment(); + } + + routeHandler = async (req: Request, res: Response) => { + const html = await render({ + req, + res, + dir: this.options.assetDir, + preloadScripts: this.getMemoryPreload, + }); + + res.send(html); + }; + + private setupDevelopment = async () => { + const webpack = requireRuntime('webpack'); + const { getConfigs } = requireRuntime(path.resolve(__dirname, '../build/index')); + + // const lazyBuild = new LazyBuild(); + + const [clientConfig, serverConfig] = getConfigs({ env: 'development' }); + // clientConfig.plugins.push(lazyBuild.createPlugin()); + + const multiCompiler = webpack([clientConfig, serverConfig]); + const [clientCompiler, serverCompiler] = multiCompiler.compilers; + + this.setupClientDev(clientCompiler); + this.setupDevServer(serverCompiler); + }; + + private setupClientDev = (compiler) => { + const wdmOptions = { + stats: false, + serverSideRender: true, + hot: true, + writeToDisk: true, + lazy: false, + publicPath: compiler.options.output.publicPath || WEBPACK_PUBLIC_PATH, + watchOptions: { ignored }, + }; + + this.wdm = devMiddleware(compiler, wdmOptions); + // this.applyExpressMiddleware(lazyBuild.createMiddleware(this.wdm)); + this.applyExpressMiddleware(this.wdm); + + const whmOptions = { + // tslint:disable-next-line:no-console + log: console.log, + path: '/__webpack_hmr', + heartbeat: 10 * 10000, + }; + + const whm = hotMiddleware(compiler, whmOptions); + this.applyExpressMiddleware(whm); + }; + + private setupDevServer = (compiler) => { + const wdmOptions = { + stats: false, + hot: false, + writeToDisk: true, + lazy: false, + watchOptions: { ignored }, + }; + + const serverWDM = devMiddleware(compiler, wdmOptions); + this.applyExpressMiddleware(serverWDM); + }; + + private getMemoryPreload: RenderParam['preloadScripts'] = async (dir, tempArray, context) => { + // sort preload with main last + await this.setMemoryStats(); + const memoryStats = mapStats(this.memoryStats, 'assetsByChunkName'); + + tempArray = mapAssets(memoryStats); + + const mainIndex = tempArray.findIndex((p) => /main/.test(p.filename)); + return [ + ...tempArray.slice(0, mainIndex), + ...tempArray.slice(mainIndex + 1), + tempArray[mainIndex], + ]; + }; + + private setMemoryStats = async () => { + const wdmPreStats = await this.waitUntilValid(this.wdm); + this.memoryStats = wdmPreStats.toJson(); + }; + + private waitUntilValid = (webpackDevMiddleware) => { + return new Promise((resolve) => { + webpackDevMiddleware.waitUntilValid(resolve); + }); + }; +} + +export { DevServer as Server }; +export default DevServer; diff --git a/packages/Maleo.js/src/server/lazy-development-build.ts b/packages/Maleo.js/src/server/lazy-development-build.ts new file mode 100644 index 00000000..b16e2845 --- /dev/null +++ b/packages/Maleo.js/src/server/lazy-development-build.ts @@ -0,0 +1,215 @@ +// Inspired by https://github.com/tfoxy/webpack-lazy-dev-build +/** + The MIT License (MIT) + + Copyright (c) 2018 Tomás Fox + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +// tslint:disable: no-console + +import { getFilenameFromUrl, handleRequest } from 'webpack-dev-middleware/lib/util'; + +/** Copied from webpack/lib/util/objectToMap.js (v4.6.0) */ +function objectToMap(obj = {}) { + const mapifiy: string[][] = Object.keys(obj).map((key) => { + /** @type {[string, string]} */ + const pair = [key, obj[key]]; + return pair; + }); + + // @ts-ignore + return new Map(mapifiy); +} + +export class LazyBuild { + requestedFiles = new Set(); + neededModules = new Set(); + + createPlugin = () => { + return new WebpackLazyBuildPlugin(this); + }; + + createMiddleware = (devMiddleware) => { + return async (req, res, next) => { + if (req.method !== 'GET') { + return next(); + } + + const { context } = devMiddleware; + const reqFilename = getFilenameFromUrl(context.options.publicPath, context.compiler, req.url); + if (reqFilename === false) { + return next(); + } + + const processFile = (filename) => { + if (context.webpackStats && !this.requestedFiles.has(filename)) { + this.requestedFiles.add(filename); + const stats = context.webpackStats.stats || [context.webpackStats]; + const modifiedStats = stats.find(({ compilation }) => { + let { outputPath } = compilation.compiler; + if (!outputPath.endsWith('/')) { + outputPath += '/'; + } + if (!filename.startsWith(outputPath)) { + return; + } + const chunkFilename = filename.slice(outputPath.length); + const filteredChunks = compilation.chunks.filter((chunk) => { + return chunk.files.includes(chunkFilename); + }); + if (!filteredChunks.length) { + return; + } + filteredChunks.forEach((chunk) => { + for (const module of chunk.modulesIterable) { + this.neededModules.add(module.resource || module.debugId); + } + }); + return true; + }); + if (modifiedStats) { + this.recompile(context, modifiedStats.compilation.compiler); + } + } + }; + + const assetUrl = this.getAssetUrl(req.url); + // CSS files handler + if (assetUrl && assetUrl !== req.url) { + const assetFilename = getFilenameFromUrl( + context.options.publicPath, + context.compiler, + assetUrl, + ); + if (assetFilename !== false) { + const assetReq = { ...req }; + assetReq.url = assetUrl; + processFile(assetFilename); + return handleRequest( + context, + assetFilename, + () => { + this.requestedFiles.add(reqFilename); + return devMiddleware(req, res, next); + }, + assetReq, + ); + } + } + + processFile(reqFilename); + return devMiddleware(req, res, next); + }; + }; + + private recompile = (context, compiler) => { + context.state = false; + const watchings = context.watching.watchings || [context.watching]; + const watching = watchings.find((w) => w.compiler === compiler); + watching.pausedWatcher = watching.watcher; + watching.watcher = null; + + let timestamps = compiler.watchFileSystem.watcher.getTimes(); + if (compiler.hooks) { + timestamps = objectToMap(timestamps); + } + compiler.fileTimestamps = timestamps; + compiler.contextTimestamps = timestamps; + + watching.invalidate(); + }; + + getAssetUrl(url) { + if (url.endsWith('.css')) { + url = url.slice(0, -4) + '.js'; + } + return url; + } +} + +class WebpackLazyBuildPlugin { + lazyBuild: LazyBuild; + + constructor(lazyBuild: LazyBuild) { + this.lazyBuild = lazyBuild; + } + + apply(compiler) { + const hasHooks = Boolean(compiler.hooks); + const compilationCallback = (compilation) => { + console.log('[Lazy-Development-Build-Plugin] - Running'); + + const buildModuleCallback = (module) => { + if (this.lazyBuild.neededModules.has(module.resource || module.debugId)) { + return; + } + + const isLazy = module.reasons.every((reason) => { + const { type } = reason.dependency; + return type === 'import()' || type === 'single entry'; + }); + + if (isLazy) { + if (hasHooks) { + const buildCopy = module.build; + module.build = () => { + console.log('[Lazy-Development-Build-Plugin] - Building', module.resource); + module.buildMeta = {}; + module.buildInfo = { + builtTime: Date.now(), + contextDependencies: module._contextDependencies, + }; + }; + setTimeout(() => { + const callbackList = compilation._buildingModules.get(module); + compilation._buildingModules.delete(module); + for (const cb of callbackList) { + cb(); + } + module.build = buildCopy; + }); + } else { + module.building = []; + setTimeout(() => { + const { building } = module; + module.building = undefined; + building.forEach((cb) => cb()); + }); + } + } else { + this.lazyBuild.neededModules.add(module.resource || module.debugId); + } + }; + + if (hasHooks) { + compilation.hooks.buildModule.tap('WebpackLazyDevBuildPlugin', buildModuleCallback); + } else { + compilation.plugin('build-module', buildModuleCallback); + } + }; + + if (hasHooks) { + compiler.hooks.compilation.tap('WebpackLazyDevBuildPlugin', compilationCallback); + } else { + compiler.plugin('compilation', compilationCallback); + } + } +} diff --git a/packages/Maleo.js/src/server/render.tsx b/packages/Maleo.js/src/server/render.tsx index 9bd289f2..314cd4bf 100644 --- a/packages/Maleo.js/src/server/render.tsx +++ b/packages/Maleo.js/src/server/render.tsx @@ -20,6 +20,7 @@ import { LoadableBundles, DocumentContext, ServerAssets, + PreloadScriptContext, } from '@interfaces/render/IRender'; import extractStats from './extract-stats'; @@ -92,21 +93,16 @@ export const defaultRenderPage = ({ req, Wrap, App, routes, data, props }: Rende }; }; -let preloadScripts: any[] = []; -export const render = async ({ req, res, dir, renderPage = defaultRenderPage }: RenderParam) => { +let preloadedAssets: any[] = []; +export const render = async ({ + req, + res, + dir, + renderPage = defaultRenderPage, + preloadScripts = defaultPreloadScripts, +}: RenderParam) => { const { document: Document, routes, wrap: Wrap, app: App } = getServerAssets(); - // sort preload with main last - if (__DEV__ || (!__DEV__ && !preloadScripts.length)) { - preloadScripts = extractStats(dir); - const mainIndex = preloadScripts.findIndex((p) => /main/.test(p.filename)); - preloadScripts = [ - ...preloadScripts.slice(0, mainIndex), - ...preloadScripts.slice(mainIndex + 1), - preloadScripts[mainIndex], - ]; - } - // matching routes const matchedRoutes = await matchingRoutes(routes, req.originalUrl); @@ -149,7 +145,11 @@ export const render = async ({ req, res, dir, renderPage = defaultRenderPage }: })(); // Loads Loadable bundle first - const scripts = [...bundles, ...preloadScripts]; + preloadedAssets = await preloadScripts(dir, preloadedAssets, { + req, + res, + }); + const scripts = [...bundles, ...preloadedAssets]; const docContext: DocumentContext = { req, @@ -250,3 +250,22 @@ const getServerAssets = (): ServerAssets => { {}, ) as ServerAssets; }; + +const defaultPreloadScripts: RenderParam['preloadScripts'] = ( + dir: string, + tempArray: any[], + context: PreloadScriptContext, +) => { + // sort preload with main last + if (!tempArray.length) { + tempArray = extractStats(dir); + const mainIndex = tempArray.findIndex((p) => /main/.test(p.filename)); + return [ + ...tempArray.slice(0, mainIndex), + ...tempArray.slice(mainIndex + 1), + tempArray[mainIndex], + ]; + } + + return tempArray; +}; diff --git a/packages/Maleo.js/src/server/server.ts b/packages/Maleo.js/src/server/server.ts index 80cf36f9..66a43f01 100644 --- a/packages/Maleo.js/src/server/server.ts +++ b/packages/Maleo.js/src/server/server.ts @@ -15,19 +15,11 @@ import path from 'path'; import helmet from 'helmet'; import { IOptions } from '@interfaces/server/IOptions'; - +import { BUILD_DIR, SERVER_ASSETS_ROUTE, CLIENT_BUILD_DIR } from '@constants/index'; import { render } from './render'; -import { - BUILD_DIR, - SERVER_ASSETS_ROUTE, - SERVER_BUILD_DIR, - CLIENT_BUILD_DIR, -} from '@constants/index'; -import { requireRuntime } from '@utils/require'; -import { AsyncRouteProps } from '@interfaces/render/IRender'; export class Server { - app: Express; + public app: Express = express(); middlewares: any[] = []; options: IOptions; @@ -38,15 +30,11 @@ export class Server { constructor(options: IOptions) { const defaultOptions = { assetDir: path.resolve('.', BUILD_DIR, CLIENT_BUILD_DIR), - routes: requireRuntime( - path.resolve('.', BUILD_DIR, SERVER_BUILD_DIR, 'routes.js'), - ) as AsyncRouteProps[], port: 8080, ...options, } as IOptions; this.options = defaultOptions; - this.app = express(); } run = async (handler) => { @@ -79,18 +67,15 @@ export class Server { // Set Compression !__DEV__ && this.setupCompression(this.app); - // Setup for development HMR, etc - __DEV__ && this.setupDevServer(this.app); - // Set secure server this.setupSecureServer(this.app); - // Set static assets route handler - this.setAssetsStaticRoute(this.app); - // Applying user's middleware this.middlewares.map((args) => this.app.use(...args)); + // Set static assets route handler + this.setAssetsStaticRoute(this.app); + // Set favicon handler this.app.use('/favicon.ico', this.faviconHandler); @@ -138,34 +123,6 @@ export class Server { app.use(compression(option)); }; - - private setupDevServer = (app: Express) => { - // Webpack Dev Server - const { getConfigs } = requireRuntime(path.resolve(__dirname, '../build/index')); - const webpack = requireRuntime('webpack'); - - const configs = getConfigs({ env: 'development' }); - const multiCompiler = webpack(configs); - - const [clientCompiler] = multiCompiler.compilers; - - const ignored = [/\.git/, /\.maleo\//, /node_modules/]; - const wdmOptions = { - stats: false, - serverSideRender: true, - hot: true, - writeToDisk: true, - publicPath: clientCompiler.options.output.publicPath || WEBPACK_PUBLIC_PATH, - watchOptions: { ignored }, - }; // @ts-ignore - app.use(requireRuntime('webpack-dev-middleware')(multiCompiler, wdmOptions)); - - const whmOptions = { - // tslint:disable-next-line:no-console - log: console.log, - path: '/__webpack_hmr', - heartbeat: 10 * 10000, - }; - app.use(requireRuntime('webpack-hot-middleware')(clientCompiler, whmOptions)); - }; } + +export default Server;