universo-platform-2d
442 строки · 12.7 Кб
1import { join } from 'node:path';
2import { fileURLToPath } from 'node:url';
3
4import type { BUILD_CONFIG_TYPE } from '@affine/env/global';
5import { PerfseePlugin } from '@perfsee/webpack';
6import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
7import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
8import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
9import CopyPlugin from 'copy-webpack-plugin';
10import { compact } from 'lodash-es';
11import MiniCssExtractPlugin from 'mini-css-extract-plugin';
12import TerserPlugin from 'terser-webpack-plugin';
13import webpack from 'webpack';
14import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
15
16import { projectRoot } from '../config/cwd.cjs';
17import type { BuildFlags } from '../config/index.js';
18import { productionCacheGroups } from './cache-group.js';
19import { WebpackS3Plugin } from './s3-plugin.js';
20
21const IN_CI = !!process.env.CI;
22
23export const rootPath = join(fileURLToPath(import.meta.url), '..', '..');
24export const workspaceRoot = join(rootPath, '..', '..', '..');
25
26const OptimizeOptionOptions: (
27buildFlags: BuildFlags
28) => webpack.Configuration['optimization'] = buildFlags => ({
29minimize: buildFlags.mode === 'production',
30minimizer: [
31new TerserPlugin({
32minify: TerserPlugin.swcMinify,
33exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
34parallel: true,
35extractComments: true,
36terserOptions: {
37ecma: 2020,
38compress: {
39unused: true,
40},
41mangle: true,
42},
43}),
44],
45removeEmptyChunks: true,
46providedExports: true,
47usedExports: true,
48sideEffects: true,
49removeAvailableModules: true,
50runtimeChunk: {
51name: 'runtime',
52},
53splitChunks: {
54chunks: 'all',
55minSize: 1,
56minChunks: 1,
57maxInitialRequests: Number.MAX_SAFE_INTEGER,
58maxAsyncRequests: Number.MAX_SAFE_INTEGER,
59cacheGroups: productionCacheGroups,
60},
61});
62
63export const getPublicPath = (buildFlags: BuildFlags) => {
64const { BUILD_TYPE } = process.env;
65if (typeof process.env.PUBLIC_PATH === 'string') {
66return process.env.PUBLIC_PATH;
67}
68
69if (
70buildFlags.mode === 'development' ||
71process.env.COVERAGE ||
72buildFlags.distribution === 'desktop'
73) {
74return '/';
75}
76
77switch (BUILD_TYPE) {
78case 'stable':
79return 'https://prod.affineassets.com/';
80case 'beta':
81return 'https://beta.affineassets.com/';
82default:
83return 'https://dev.affineassets.com/';
84}
85};
86
87export const createConfiguration: (
88cwd: string,
89buildFlags: BuildFlags,
90buildConfig: BUILD_CONFIG_TYPE
91) => webpack.Configuration = (cwd, buildFlags, buildConfig) => {
92const config = {
93name: 'affine',
94// to set a correct base path for the source map
95context: cwd,
96experiments: {
97topLevelAwait: true,
98outputModule: false,
99syncWebAssembly: true,
100},
101output: {
102environment: {
103module: true,
104dynamicImport: true,
105},
106filename:
107buildFlags.mode === 'production'
108? 'js/[name].[contenthash:8].js'
109: 'js/[name].js',
110// In some cases webpack will emit files starts with "_" which is reserved in web extension.
111chunkFilename: pathData =>
112pathData.chunk?.name === 'worker'
113? 'js/worker.[contenthash:8].js'
114: buildFlags.mode === 'production'
115? 'js/chunk.[name].[contenthash:8].js'
116: 'js/chunk.[name].js',
117assetModuleFilename:
118buildFlags.mode === 'production'
119? 'assets/[name].[contenthash:8][ext][query]'
120: '[name].[contenthash:8][ext]',
121devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
122hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
123hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
124path: join(cwd, 'dist'),
125clean: buildFlags.mode === 'production',
126globalObject: 'globalThis',
127// NOTE(@forehalo): always keep it '/'
128publicPath: '/',
129workerPublicPath: '/',
130},
131target: ['web', 'es2022'],
132
133mode: buildFlags.mode,
134
135devtool:
136buildFlags.mode === 'production'
137? 'source-map'
138: 'eval-cheap-module-source-map',
139
140resolve: {
141symlinks: true,
142extensionAlias: {
143'.js': ['.js', '.tsx', '.ts'],
144'.mjs': ['.mjs', '.mts'],
145},
146extensions: ['.js', '.ts', '.tsx'],
147alias: {
148yjs: join(workspaceRoot, 'node_modules', 'yjs'),
149lit: join(workspaceRoot, 'node_modules', 'lit'),
150},
151},
152
153module: {
154parser: {
155javascript: {
156// Do not mock Node.js globals
157node: false,
158requireJs: false,
159import: true,
160// Treat as missing export as error
161strictExportPresence: true,
162},
163},
164rules: [
165{
166test: /\.m?js?$/,
167resolve: {
168fullySpecified: false,
169},
170},
171{
172test: /\.js$/,
173enforce: 'pre',
174include: /@blocksuite/,
175use: ['source-map-loader'],
176},
177{
178oneOf: [
179{
180test: /\.ts$/,
181exclude: /node_modules/,
182loader: 'swc-loader',
183options: {
184// https://swc.rs/docs/configuring-swc/
185jsc: {
186preserveAllComments: true,
187parser: {
188syntax: 'typescript',
189dynamicImport: true,
190topLevelAwait: false,
191tsx: false,
192decorators: true,
193},
194target: 'es2022',
195externalHelpers: false,
196transform: {
197useDefineForClassFields: false,
198decoratorVersion: '2022-03',
199},
200},
201sourceMaps: true,
202inlineSourcesContent: true,
203},
204},
205{
206test: /\.tsx$/,
207exclude: /node_modules/,
208loader: 'swc-loader',
209options: {
210// https://swc.rs/docs/configuring-swc/
211jsc: {
212preserveAllComments: true,
213parser: {
214syntax: 'typescript',
215dynamicImport: true,
216topLevelAwait: false,
217tsx: true,
218decorators: true,
219},
220target: 'es2022',
221externalHelpers: false,
222transform: {
223react: {
224runtime: 'automatic',
225refresh: buildFlags.mode === 'development' && {
226refreshReg: '$RefreshReg$',
227refreshSig: '$RefreshSig$',
228emitFullSignatures: true,
229},
230},
231useDefineForClassFields: false,
232decoratorVersion: '2022-03',
233},
234},
235sourceMaps: true,
236inlineSourcesContent: true,
237},
238},
239{
240test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/,
241type: 'asset/resource',
242},
243{
244test: /\.(ttf|eot|woff|woff2)$/,
245type: 'asset/resource',
246},
247{
248test: /\.txt$/,
249type: 'asset/source',
250},
251{
252test: /\.inline\.svg$/,
253type: 'asset/inline',
254},
255{
256test: /\.css$/,
257use: [
258buildFlags.mode === 'development'
259? 'style-loader'
260: MiniCssExtractPlugin.loader,
261{
262loader: 'css-loader',
263options: {
264url: true,
265sourceMap: false,
266modules: false,
267import: true,
268importLoaders: 1,
269},
270},
271{
272loader: 'postcss-loader',
273options: {
274postcssOptions: {
275config: join(rootPath, 'webpack', 'postcss.config.cjs'),
276},
277},
278},
279],
280},
281],
282},
283],
284},
285plugins: compact([
286IN_CI ? null : new webpack.ProgressPlugin({ percentBy: 'entries' }),
287buildFlags.mode === 'development'
288? new ReactRefreshWebpackPlugin({
289overlay: false,
290esModule: true,
291include: /\.tsx$/,
292})
293: // todo: support multiple entry points
294new MiniCssExtractPlugin({
295filename: `[name].[contenthash:8].css`,
296ignoreOrder: true,
297}),
298new VanillaExtractPlugin(),
299new webpack.DefinePlugin({
300'process.env.NODE_ENV': JSON.stringify(buildFlags.mode),
301'process.env.CAPTCHA_SITE_KEY': JSON.stringify(
302process.env.CAPTCHA_SITE_KEY
303),
304'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN),
305'process.env.BUILD_TYPE': JSON.stringify(process.env.BUILD_TYPE),
306'process.env.MIXPANEL_TOKEN': JSON.stringify(
307process.env.MIXPANEL_TOKEN
308),
309'process.env.DEBUG_JOTAI': JSON.stringify(process.env.DEBUG_JOTAI),
310...Object.entries(buildConfig).reduce(
311(def, [k, v]) => {
312def[`BUILD_CONFIG.${k}`] = JSON.stringify(v);
313return def;
314},
315{} as Record<string, string>
316),
317}),
318buildFlags.distribution === 'admin'
319? null
320: new CopyPlugin({
321patterns: [
322{
323// copy the shared public assets into dist
324from: join(
325workspaceRoot,
326'packages',
327'frontend',
328'core',
329'public'
330),
331to: join(cwd, 'dist'),
332},
333],
334}),
335buildFlags.mode === 'production' && process.env.R2_SECRET_ACCESS_KEY
336? new WebpackS3Plugin()
337: null,
338]),
339stats: {
340errorDetails: true,
341},
342
343optimization: OptimizeOptionOptions(buildFlags),
344
345devServer: {
346hot: buildFlags.static ? false : 'only',
347liveReload: !buildFlags.static,
348client: {
349overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
350},
351historyApiFallback: true,
352static: [
353{
354directory: join(
355projectRoot,
356'packages',
357'frontend',
358'core',
359'public'
360),
361publicPath: '/',
362watch: !buildFlags.static,
363},
364{
365directory: join(cwd, 'public'),
366publicPath: '/',
367watch: !buildFlags.static,
368},
369],
370proxy: [
371{
372context: '/api/worker/',
373target: 'https://affine.fail',
374changeOrigin: true,
375secure: false,
376},
377{ context: '/api', target: 'http://localhost:3010' },
378{ context: '/socket.io', target: 'http://localhost:3010', ws: true },
379{ context: '/graphql', target: 'http://localhost:3010' },
380],
381} as DevServerConfiguration,
382} satisfies webpack.Configuration;
383
384if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) {
385config.plugins.push(
386new PerfseePlugin({
387project: 'affine-toeverything',
388})
389);
390}
391
392if (buildFlags.mode === 'development') {
393config.optimization = {
394...config.optimization,
395minimize: false,
396runtimeChunk: false,
397splitChunks: {
398maxInitialRequests: Infinity,
399chunks: 'all',
400cacheGroups: {
401errorHandler: {
402test: /global-error-handler/,
403priority: 1000,
404enforce: true,
405},
406defaultVendors: {
407test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`,
408priority: -10,
409reuseExistingChunk: true,
410},
411default: {
412minChunks: 2,
413priority: -20,
414reuseExistingChunk: true,
415},
416styles: {
417name: 'styles',
418type: 'css/mini-extract',
419chunks: 'all',
420enforce: true,
421},
422},
423},
424};
425}
426
427if (
428process.env.SENTRY_AUTH_TOKEN &&
429process.env.SENTRY_ORG &&
430process.env.SENTRY_PROJECT
431) {
432config.plugins.push(
433sentryWebpackPlugin({
434org: process.env.SENTRY_ORG,
435project: process.env.SENTRY_PROJECT,
436authToken: process.env.SENTRY_AUTH_TOKEN,
437})
438);
439}
440
441return config;
442};
443