Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update blank-typescript example with rspack #2951

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/blank-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"name": "@botonic/example-blank-typescript",
"version": "0.30.0",
"scripts": {
"build": "webpack --env target=all --mode=production",
"start": "webpack-dev-server --env target=dev --mode=development",
"build": "ENVIRONMENT=production NODE_ENV=production rspack build --env target=all --mode=production",
"start": "ENVIRONMENT=local NODE_ENV=development rspack serve --env target=dev --mode=development",
"deploy": "botonic deploy -c build",
"test": "jest"
},
Expand All @@ -12,6 +12,7 @@
"@botonic/react": "^0.30.0"
},
"devDependencies": {
"@botonic/dx": "^0.30.0"
"@botonic/dx": "^0.30.0",
"@botonic/dx-bundler-rspack": "^0.30.0-alpha.4"
}
}
372 changes: 372 additions & 0 deletions examples/blank-typescript/rspack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const rspack = require('@rspack/core')
const ReactRefreshPlugin = require('@rspack/plugin-react-refresh')
const ROOT_PATH = path.resolve(__dirname, 'src')
const OUTPUT_PATH = path.resolve(__dirname, 'dist')
const WEBVIEWS_PATH = path.resolve(OUTPUT_PATH, 'webviews')
const BOTONIC_PATH = path.resolve(
__dirname,
'node_modules',
'@botonic',
'react'
)

const BOTONIC_TARGET = Object.freeze({
ALL: 'all',
DEV: 'dev',
NODE: 'node',
WEBVIEWS: 'webviews',
WEBCHAT: 'webchat',
})

const WEBPACK_ENTRIES_DIRNAME = 'webpack-entries'
const WEBPACK_ENTRIES = {
DEV: 'dev-entry.ts',
NODE: 'node-entry.ts',
WEBCHAT: 'webchat-entry.ts',
WEBVIEWS: 'webviews-entry.ts',
}

const TEMPLATES = {
WEBCHAT: 'webchat.template.html',
WEBVIEWS: 'webview.template.html',
}

const UMD_LIBRARY_TARGET = 'umd'

const BOTONIC_LIBRARY_NAME = 'Botonic'
const WEBVIEW_LIBRARY_NAME = 'BotonicWebview'
const BOT_LIBRARY_NAME = 'bot'

const WEBCHAT_FILENAME = 'webchat.botonic.js'
const WEBVIEWS_FILENAME = 'webviews.js'
const BOT_FILENAME = 'bot.js'

const MODE_DEV = 'development'
const MODE_PROD = 'production'
const hubtypeDefaults = {
API_URL: 'https://api.hubtype.com', // pragma: allowlist secret
WEBCHAT_PUSHER_KEY: '434ca667c8e6cb3f641c', // pragma: allowlist secret
}

const CONFIG_ENVIRONMENTS = ['production', 'staging', 'local']

const resolveConfig = {
extensions: ['.*', '.js', '.jsx', '.ts', '.tsx'],
alias: {
BotonicProject: path.resolve(__dirname, 'src'),
react: path.resolve('./node_modules/react'),
// 'styled-components': path.resolve('./node_modules/styled-components'),
'@botonic/react': BOTONIC_PATH,
},
}

const isDev = String(process.env.ENVIRONMENT) === 'local'

const typescriptLoaderConfig =
// exclude: /node_modules[/\\](?!(@botonic\/(core|react))[/\\])/,
[
{
test: /\.tsx?$/,
exclude: [/node_modules/],
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
keepClassNames: true,
},
},
},
type: 'javascript/auto',
},
{
test: /\.jsx?$/,
exclude: [/node_modules/],
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true,
},
transform: {
react: {
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment',
throwIfNamespace: true,
development: isDev,
refresh: isDev,
useBuiltins: false,
},
},
keepClassNames: true,
},
},
},
type: 'javascript/auto',
},
]

const fileLoaderConfig = [
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset/resource',
},
{
test: /^BUILD_ID$/,
type: 'asset/source',
},
]

const nullLoaderConfig = {
test: /\.(scss|css)$/,
use: 'null-loader',
}

const stylesLoaderConfig = [
{
test: /\.(sass|scss|css)$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
type: 'javascript/auto',
},
]

function log(message) {
// using errors to avoid screwing up webpack-bundle-analyzer when running with --profile
console.error(message)
}

function getHtmlWebpackPlugin(templateName) {
return new HtmlWebpackPlugin({
template: path.resolve(BOTONIC_PATH, 'src', templateName),
filename: 'index.html',
})
}

function getConfigEnvironment() {
const configEnvironment = String(process.env.ENVIRONMENT).toLowerCase()
if (!CONFIG_ENVIRONMENTS.includes(configEnvironment)) {
console.error(
`You need to set env var ENVIRONMENT to one of these: ${CONFIG_ENVIRONMENTS}. Current value: ${configEnvironment}`
)
// eslint-disable-next-line no-process-exit
process.exit(1)
}
return configEnvironment
}

function getPlugins(mode, target) {
const environment = getConfigEnvironment()
log(
`Generating bundle for: ${target}\nWebpack running on mode '${mode}' with env var ENVIRONMENT set to: ${environment}`
)

const plugins = [
new rspack.EnvironmentPlugin({
NODE_ENV: process.env.NODE_ENV,
HUBTYPE_API_URL: process.env.HUBTYPE_API_URL || hubtypeDefaults.API_URL,
BOTONIC_TARGET: 'node',
WEBCHAT_PUSHER_KEY:
process.env.WEBCHAT_PUSHER_KEY || hubtypeDefaults.WEBCHAT_PUSHER_KEY,
ENVIRONMENT: process.env.ENVIRONMENT,
}),
new rspack.ProgressPlugin(),
]

if (isDev) {
plugins.push(new ReactRefreshPlugin())
}

return plugins
}

function sourceMap(mode) {
if (mode === MODE_PROD) {
return 'hidden-source-map'
} else if (mode === MODE_DEV) {
return 'eval-cheap-source-map'
} else {
throw new Error(
'Invalid mode argument (' + mode + '). See package.json scripts'
)
}
}

// Not work for Webviews, not keep fnames
const minimizerPlugin = new rspack.SwcJsMinimizerRspackPlugin({
minimizerOptions: {
minify: true,
compress: {
keep_classnames: true,
keep_fargs: false,
keep_fnames: true,
},
mangle: {
keep_fnames: true,
keep_classnames: true,
},
},
})

function botonicDevConfig(mode) {
return {
optimization: {
minimize: false,
},
mode,
devtool: sourceMap(mode),
target: 'web',
entry: path.resolve(WEBPACK_ENTRIES_DIRNAME, WEBPACK_ENTRIES.DEV),
module: {
rules: [
...typescriptLoaderConfig,
...fileLoaderConfig,
...stylesLoaderConfig,
],
},
output: {
path: OUTPUT_PATH,
filename: WEBCHAT_FILENAME,
library: BOTONIC_LIBRARY_NAME,
libraryTarget: UMD_LIBRARY_TARGET,
libraryExport: 'app',
assetModuleFilename: 'assets/[hash][ext][query]',
},
resolve: resolveConfig,
devServer: {
static: [OUTPUT_PATH],
liveReload: true,
historyApiFallback: true,
hot: true,
},
plugins: [
getHtmlWebpackPlugin(TEMPLATES.WEBCHAT),
new NodePolyfillPlugin({ includeAliases: ['stream'] }),
new rspack.NormalModuleReplacementPlugin(/node:stream/, resource => {
resource.request = resource.request.replace(/^node:/, '')
}),
...getPlugins(mode, BOTONIC_TARGET.DEV),
],
}
}

function botonicWebchatConfig(mode) {
return {
optimization: {
minimize: true,
minimizer: [minimizerPlugin],
},
mode,
devtool: sourceMap(mode),
target: 'web',
entry: path.resolve(WEBPACK_ENTRIES_DIRNAME, WEBPACK_ENTRIES.WEBCHAT),
output: {
path: OUTPUT_PATH,
filename: WEBCHAT_FILENAME,
library: BOTONIC_LIBRARY_NAME,
libraryTarget: UMD_LIBRARY_TARGET,
libraryExport: 'app',
assetModuleFilename: 'assets/[hash][ext][query]',
},
module: {
rules: [
...typescriptLoaderConfig,
...fileLoaderConfig,
...stylesLoaderConfig,
],
},
resolve: resolveConfig,
plugins: [
getHtmlWebpackPlugin(TEMPLATES.WEBCHAT),
...getPlugins(mode, BOTONIC_TARGET.WEBCHAT),
],
}
}

function botonicWebviewsConfig(mode) {
return {
optimization: {
sideEffects: true, // critical so that tree-shaking discards browser code from @botonic/react
usedExports: true,
minimize: true,
minimizer: mode === MODE_PROD ? [minimizerPlugin] : [],
},
mode,
devtool: sourceMap(mode),
target: 'web',
entry: path.resolve(WEBPACK_ENTRIES_DIRNAME, WEBPACK_ENTRIES.WEBVIEWS),
output: {
path: WEBVIEWS_PATH,
filename: WEBVIEWS_FILENAME,
library: WEBVIEW_LIBRARY_NAME,
libraryTarget: UMD_LIBRARY_TARGET,
libraryExport: 'app',
assetModuleFilename: '../assets/[hash][ext][query]',
},
module: {
rules: [
...typescriptLoaderConfig,
...fileLoaderConfig,
...stylesLoaderConfig,
],
},
resolve: resolveConfig,
plugins: [
getHtmlWebpackPlugin(TEMPLATES.WEBVIEWS),
...getPlugins(mode, BOTONIC_TARGET.WEBVIEWS),
],
}
}

function botonicServerConfig(mode) {
return {
optimization: {
sideEffects: true, // critical so that tree-shaking discards browser code from @botonic/react
minimize: true,
minimizer: mode === MODE_PROD ? [minimizerPlugin] : [],
},
context: ROOT_PATH,
// 'mode' removed so that we're forced to be explicit
target: 'node',
entry: path.resolve(WEBPACK_ENTRIES_DIRNAME, WEBPACK_ENTRIES.NODE),
output: {
filename: BOT_FILENAME,
library: BOT_LIBRARY_NAME,
libraryTarget: 'umd',
libraryExport: 'app',
assetModuleFilename: 'assets/[hash][ext][query]',
},
module: {
rules: [...typescriptLoaderConfig, ...fileLoaderConfig, nullLoaderConfig],
},
resolve: resolveConfig,
plugins: getPlugins(mode, BOTONIC_TARGET.NODE),
}
}

module.exports = function (env, argv) {
if (env.target === BOTONIC_TARGET.ALL) {
return [
botonicServerConfig(argv.mode),
botonicWebviewsConfig(argv.mode),
botonicWebchatConfig(argv.mode),
]
} else if (env.target === BOTONIC_TARGET.DEV) {
return [botonicDevConfig(argv.mode)]
} else if (env.target === BOTONIC_TARGET.NODE) {
return [botonicServerConfig(argv.mode)]
} else if (env.target === BOTONIC_TARGET.WEBVIEWS) {
return [botonicWebviewsConfig(argv.mode)]
} else if (env.target === BOTONIC_TARGET.WEBCHAT) {
return [botonicWebchatConfig(argv.mode)]
}
throw new Error(`Invalid target ${env.target}`)
}
Loading
Loading