Last active
May 7, 2020 02:16
-
-
Save Luiz-Monad/366d43f9a762317ffa824bff364b35be to your computer and use it in GitHub Desktop.
webpack server side rendering (all rights reserved)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Browser.Types | |
open Browser.Dom | |
open Node | |
let private proc = Api.``process`` | |
// Node Application Entry | |
let mutable runApp = null | |
match proc.env?SSR, proc.env?MODE with | |
//:: WDS :: | |
| _, "development" -> | |
appMain placeholderId theme | |
//:: Node :: | |
| "ssr", _ -> | |
PageList.pages.Keys |> Seq.iter id //for sidefx | |
let out = renderHtmlString "" "" | |
runApp <- box out | |
//:: Broswer :: | |
| _ -> | |
appMain placeholderId theme | |
//UMD | |
exportDefault runApp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Template for webpack.config.js in Fable projects | |
// Find latest version in https://github.com/fable-compiler/webpack-config-template | |
// In most cases, you'll only need to edit the CONFIG object (after dependencies) | |
// See below if you need better fine-tuning of Webpack options | |
// Dependencies. Also required: core-js, fable-loader, fable-compiler, @babel/core, @babel/preset-env, babel-loader | |
const webpack = require('webpack'); | |
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); | |
const util = require('./webpack.config.util') | |
const localResolve = util.resolve(__dirname); | |
module.exports = (options) => { | |
const resolve = util.resolve(options.dirName); | |
const baseConfig = { | |
dirName: options.dirName, | |
// The tags to include the generated JS and CSS will be automatically injected in the HTML template | |
// See https://github.com/jantimon/html-webpack-plugin | |
indexHtmlTemplate: localResolve('./webpack.config.ssr.val.js'), | |
fsharpDefine: options.fsharpDefine, | |
fsharpEntry: resolve(options.fsharpEntry), | |
srcDir: resolve('.'), | |
outputDir: resolve(options.outputDir), | |
assetsDir: resolve(options.assetsDir), | |
devServerHost: options.devServerHost, | |
devServerPort: options.devServerPort, | |
// When using webpack-dev-server, you may need to redirect some calls | |
// to a external API server. See https://webpack.js.org/configuration/dev-server/#devserver-proxy | |
devServerProxy: { | |
// redirect requests that start with /api/* to the server on port port | |
'/api/*': { | |
target: 'http://localhost:' + options.devServerProxy, | |
changeOrigin: true | |
} | |
}, | |
// Use babel-preset-env to generate JS compatible with most-used browsers. | |
// More info at https://babeljs.io/docs/en/next/babel-preset-env.html | |
babel: { | |
presets: [ | |
['@babel/preset-env', { | |
modules: false, | |
// This adds polyfills when needed. Requires core-js dependency. | |
// See https://babeljs.io/docs/en/babel-preset-env#usebuiltins | |
// Note that you still need to add custom polyfills if necessary (e.g. whatwg-fetch) | |
useBuiltIns: 'usage', | |
corejs: 3, | |
}] | |
], | |
} | |
}; | |
// If we're running the webpack-dev-server, assume we're in development mode | |
const isProduction = !process.argv.find(v => v.indexOf('webpack-dev-server') !== -1); | |
console.log('Bundling for ' + (isProduction ? 'production' : 'development') + '...'); | |
// The HtmlWebpackPlugin allows us to use a template for the index.html page | |
// and automatically injects <script> or <link> tags for generated bundles. | |
const commonPlugins = [ | |
new webpack.EnvironmentPlugin({ | |
MODE: (isProduction ? 'production' : 'development') | |
}), | |
new HtmlWebpackPlugin({ | |
filename: 'index.html', | |
template: baseConfig.indexHtmlTemplate, | |
inlineSource: '^.+\.(css)$', | |
}), | |
new HtmlWebpackInlineSourcePlugin(), | |
]; | |
return { | |
// In development, split the JavaScript files in order to | |
// have a faster HMR support. | |
entry: { | |
app: [baseConfig.fsharpEntry], | |
}, | |
// Add a hash to the output file name in production | |
// to prevent browser caching if code changes | |
output: { | |
path: baseConfig.outputDir, | |
pathinfo: !isProduction, | |
chunkFilename: isProduction ? '[name].[hash].js' : '[name].js', | |
filename: isProduction ? '[name].[hash].js' : '[name].js', | |
}, | |
mode: isProduction ? 'production' : 'development', | |
devtool: isProduction ? 'source-map' : 'eval-source-map', //'inline-cheap-source-map', | |
context: baseConfig.srcDir, | |
optimization: { | |
minimize: isProduction, | |
nodeEnv: isProduction ? 'production' : 'development', | |
splitChunks: { | |
chunks: 'async', | |
cacheGroups: { | |
app: { | |
test: /[\\/]src[\\/]app[\\/]/i, | |
name: "app", | |
reuseExistingChunk: true, | |
enforce: true, | |
}, | |
async: { | |
test: /[\\/]src[\\/]hydrate[\\/]/i, | |
name: "hydrate", | |
reuseExistingChunk: true, | |
enforce: true, | |
}, | |
}, | |
}, | |
}, | |
target: 'web', | |
// Besides the HtmlPlugin, we use the following plugins: | |
// PRODUCTION | |
// DEVELOPMENT | |
// - HotModuleReplacementPlugin: Enables hot reloading when code changes without refreshing | |
plugins: isProduction | |
? commonPlugins.concat([ | |
]) | |
: commonPlugins.concat([ | |
new webpack.HotModuleReplacementPlugin(), | |
]), | |
resolve: { | |
// See https://github.com/fable-compiler/Fable/issues/1490 | |
symlinks: false, | |
modules: [ | |
baseConfig.srcDir, | |
baseConfig.assetsDir, | |
baseConfig.outputDir, | |
'node_modules', | |
], | |
}, | |
// Configuration for webpack-dev-server | |
devServer: { | |
publicPath: '/', | |
contentBase: baseConfig.assetsDir, | |
host: baseConfig.devServerHost, | |
port: baseConfig.devServerPort, | |
proxy: baseConfig.devServerProxy, | |
hot: true, | |
inline: true, | |
}, | |
// - cache-loader: caches transformed files. | |
// - fable-loader: transforms F# into JS. | |
// - babel-loader: transforms JS to old syntax (compatible with old browsers). | |
// - file-loader: moves files referenced in the code (fonts, images) into output folder. | |
// - css-loader: parses css, transforms imports to JS requires. | |
// - html-loader: parses html, transforms srcs/hrefs to JS requires. | |
// - extract-loader: requires resources as dependencies (from css/html loaders). | |
// - raw-loader: transforms previous text resource into a JS module. | |
// - val-loader: runs external commands to generate assets. | |
module: { | |
rules: [ | |
{ | |
test: /\.fs(x|proj)?$/, | |
use: [{ | |
loader: 'cache-loader', | |
}, { | |
loader: 'fable-loader', | |
options: { | |
babel: baseConfig.babel, | |
define: baseConfig.fsharpDefine.concat( | |
isProduction ? [] : ["DEBUG"]), | |
extra: { optimizeWatch: !isProduction } | |
} | |
}] | |
}, | |
{ | |
test: /\.js$/, | |
exclude: /node_modules/, | |
use: [{ | |
loader: 'babel-loader', | |
options: baseConfig.babel, | |
}] | |
}, | |
{ | |
type: 'javascript/auto', | |
test: /\.json$/, | |
use: [{ | |
loader: 'file-loader', | |
}] | |
}, | |
{ | |
test: /\.css$/, | |
use: [{ | |
loader: 'file-loader', | |
}, { | |
loader: 'extract-loader', | |
}, { | |
loader: 'css-loader', | |
}] | |
}, | |
{ | |
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*)?$/, | |
use: [{ | |
loader: 'file-loader', | |
}] | |
}, | |
{ | |
test: /\.val\.js$/, | |
use: [{ | |
loader: 'html-loader', | |
options: { | |
minimize: true, | |
attributes: { | |
root: baseConfig.assetsDir, | |
list: [ | |
{ | |
tag: 'link', | |
attribute: 'href', | |
type: 'src', | |
filter: util.filter_extracted_css, | |
} | |
], | |
urlFilter: util.urlFilter_ssr, | |
}, | |
preprocessor: util.extract_css, | |
} | |
}, { | |
loader: 'val-loader', | |
options: { | |
config: options, | |
} | |
}] | |
} | |
] | |
}, | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Config for Node SSR. | |
const webpack = require('webpack'); | |
const util = require('./webpack.config.util'); | |
module.exports = (configBase) => { | |
console.log('Bundling for server-side-rendering...'); | |
const config = configBase; | |
return ({ | |
entry: { | |
ssr: config.entry.app, | |
}, | |
output: { | |
path: config.output.path, | |
pathinfo: config.output.pathinfo, | |
chunkFilename: '[name].js', | |
filename: '[name].js', | |
libraryTarget: 'umd', | |
}, | |
mode: config.mode, | |
context: config.context, | |
optimization: { | |
nodeEnv: config.mode, | |
minimize: false, | |
removeAvailableModules: false, | |
removeEmptyChunks: false, | |
mergeDuplicateChunks: false, | |
splitChunks: false, | |
}, | |
target: 'node', | |
plugins: ([ | |
new webpack.EnvironmentPlugin({ | |
SSR: 'ssr', | |
}), | |
new webpack.optimize.LimitChunkCountPlugin({ | |
maxChunks: 1 | |
}), | |
]), | |
resolve: config.resolve, | |
module: util.replaceLoader(config.module, { | |
'val-loader': null, | |
}), | |
}); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const CONFIG_BASE = './webpack.config'; | |
const CONFIG_SSR = './webpack.config.ssr'; | |
const path = require('path'); | |
const webpack = require('webpack'); | |
const MemoryFS = require('memory-fs'); | |
const Module = require('module'); | |
const fs = new MemoryFS(); | |
const outputStats = (logger, err, stats) => { | |
if (err) { | |
logger.error(err) | |
return true; | |
} | |
const info = stats.toJson('verbose'); | |
(info.errors || []).forEach(logger.error.bind(logger)); | |
(info.warnings || []).forEach(logger.warn.bind(logger)); | |
logger.log(stats.toString({ | |
colors: true | |
})); | |
return false; | |
}; | |
const parentModule = module; | |
const exec = (code, loaderContext) => { | |
const { resource, context } = loaderContext; | |
const module = new Module(resource, parentModule); | |
module.paths = Module._nodeModulePaths(context); | |
module.filename = resource; | |
module._compile(code, resource); | |
return module.exports; | |
} | |
module.exports = (options, loaderContext) => { | |
const logger = loaderContext.getLogger('SSR') | |
return new Promise((resolve, reject) => { | |
logger.log('SSR: Compiling ...'); | |
const configBase = path.join(__dirname, CONFIG_BASE); | |
const configSSR = path.join(__dirname, CONFIG_SSR); | |
loaderContext.addDependency(configBase); | |
loaderContext.addDependency(configSSR); | |
const webpackConfBase = require(configBase); | |
const webpackConf = require(configSSR); | |
const serverCompiler = webpack(webpackConf(webpackConfBase(options.config))); | |
serverCompiler.outputFileSystem = fs; | |
serverCompiler.run((err, stats) => { | |
if (outputStats(logger, err, stats)) { | |
reject(new Error('Webpack failed.')); | |
return; | |
} | |
const assets = stats.compilation.assets; | |
const fileDeps = stats.compilation.fileDependencies; | |
const ctxDeps = stats.compilation.contextDependencies; | |
const entry = stats.compilation.entrypoints.keys().next().value; | |
const asset = Object.keys(assets).reduce((_, a) => a.startsWith(entry) ? a : null); | |
const content = assets[asset].source(); | |
const extras = Object.keys(assets).filter((a) => !a.startsWith(entry)); | |
let modl; | |
try { | |
modl = exec(content, loaderContext); | |
} catch (err) { | |
reject(new Error(`Failure compiling "${entry}": ${err}`)); | |
return; | |
} | |
let output; | |
try { | |
output = modl[Object.keys(modl)[0]](); | |
} catch (err) { | |
reject(new Error(`Failure running "${entry}": ${err}`)) | |
return; | |
} | |
extras.forEach(e => loaderContext.emitFile(e, assets[e].source())); | |
logger.log('SSR: Done'); | |
resolve({ | |
code: output, | |
dependencies: fileDeps, | |
contextDependencies: ctxDeps, | |
cacheable: true, | |
}) | |
}); | |
}) | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment