ã¯ããã« ããã«ã¡ã¯ãã€ã³ã¿ãŒã³çã®æå¡ã§ãã ä»åã¯GROWIã«ãããwebpackã®èšå®ã«ã€ããŠã調ã¹ãŠã¿ãã®ã§èšäºã«ããŸãããã®èšäºã¯GROWIã«ãããwebpackã®èšå®ã«çç®ããŠããã®ã§webpackã®åºç€ç¥èããäœ¿ãæ¹ã®è©³çްã¯èª¬æããŠããŸãããwebpackã«ã€ããŠããçšåºŠã®ç¥èããã人ã«ããããžã§ã¯ããžã®æŽ»çšäŸãšããŠåèã«ããŠããããã°ãªãšæã£ãŠãããŸãã webpackã¯èšå®ãè€éã§ããã®ãããwebpackè·äººããšåŒã°ãã人ãã¡ãååšããŸããæ¬åœã¯ã誰ããç°¡åã«èšå®ã§ããã®ãçæ³ã§ãããNext.jsãªã©ã§ã¯äžéšãèªåçã«ãã£ãŠãããããããŠããŸããã§ããããŸã webpackãwebéçºã«ãããã¢ãžã¥ãŒã«ãã³ãã©ãŒãšããŠå€ãçšããããŠããã®ã¯äºå®ã§ãããwebpackã®ç¥èã¯æã£ãŠããŠæã¯ãªãã§ãããããšããããšãå
茩ã«èšãããã®ã§ããããæãã§å匷ããŸããã ããããwebpackãšã¯ å
¬åŒããã¥ã¡ã³ã https://webpack.js.org/ webpackã¯ãããããã¢ãžã¥ãŒã«ãã³ãã©ãŒããšåŒã°ãããã®ã§ãèšå®ãã¡ã€ã«ã®æç€ºã«åºã¥ããŠè€æ°ã®JSãã¡ã€ã«ãCSSãã¡ã€ã«ãç»åãã¡ã€ã«ãªã©ãäžã€ã«ãŸãšããæ©èœãæã£ãŠããŸãããã©ãŠã¶ãäŸã«åºããšãã¢ãžã¥ãŒã«ãã³ãã©ãŒã掻çšããããšã§èªã¿èŸŒããã¡ã€ã«ã®æ°ãå°ãªããªã£ããããã³ãã«åãããéã«ç¡é§ãªè¡ãçããããããŠå¹çãããã¡ã€ã«ãèªã¿èŸŒãããšãã§ããŸãããããŠããã®ã¢ãžã¥ãŒã«ãã³ãã©ãŒã®çé ããwebpackããšããããã§ãã GROWIã«ãããwebpackã®äœ¿ãæ¹ã®æŠèŠã»ã€ã¡ãŒãž ãã®èšäºã¯webpack4ç³»ã«é¢ããæ
å ±ã«ãªããŸãã2022幎6ææç¹ã§ã®ææ°çã¯5.73.0ãªã®ã§ãææ°ã®æ
å ±ã¯å
¬åŒããã¥ã¡ã³ãã確èªããŠãã ãã https://webpack.js.org/ GROWIã§ã¯éçºçšãšè£œåçšã®2çš®é¡ã®webpackèšå®ããããããããã®å
±éèšå®ããŸãšãããã¡ã€ã«ããããŸããéçºçšãšè£œåçšã®èšå®ãã¡ã€ã«ã§ããããå
±éã®åŠçãåŒã³åºããŠããã€ã¡ãŒãžã§ãããªã®ã§webpackèšå®é¢é£ã®ãã¡ã€ã«ã¯ webpack.common.js webpack.dev.js webpack.prod.js ã®3ã€ã«ãªããŸãã ãããŠãããããã®ãã¡ã€ã«ã«åŸã£ãŠJSãã¡ã€ã«ãCSSãã¡ã€ã«ãç»åãã¡ã€ã«ããŸãšããäžã§ããã®ãŸãšãããããã¡ã€ã«ããã©ãŠã¶äžã§èªã¿èŸŒãããšã§ã¢ããªãåããŠããŸããããããã¯GROWIã®å®éã®èšå®ãã¡ã€ã«ã®äžã§ãã¡ã€ã³ãšãªãå
±éã®èšå®ãã¡ã€ã«ãã¿ãŠèª¬æããŠãããŸããwebpackã®èšå®ã¯ãããããããŸãããã®èšäºã¯ãããŸã§GROWIã®èšå®ã®ç޹ä»ãªã®ã§ç»å Žããªãèšå®ããããŸãããäºæ¿ãã ããã GROWIã«ãããwebpackã®è©³çްèšå® ãããå
±éã®ãã¡ã€ã«ã§ãã // webpack.common.js const path = require('path'); const webpack = require('webpack'); /* * Webpack Plugins */ const WebpackAssetsManifest = require('webpack-assets-manifest'); const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); /* * Webpack configuration * */ module.exports = (options) => { return { mode: options.mode, entry: Object.assign({ 'js/boot': './src/client/boot', // ~~çç¥~~ 'styles/style-hackmd': './src/styles-hackmd/style.scss', }, options.entry || {}), // Merge with env dependent settings output: Object.assign({ path: path.resolve(__dirname, '../public'), publicPath: '/', filename: '[name].bundle.js', }, options.output || {}), // Merge with env dependent settings externals: { jquery: 'jQuery', emojione: 'emojione', hljs: 'hljs', 'dtrace-provider': 'dtrace-provider', }, resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], plugins: [ new TsconfigPathsPlugin({ configFile: path.resolve(__dirname, '../tsconfig.build.client.json'), extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], }), ], }, node: { fs: 'empty', }, module: { rules: options.module.rules.concat([ // ~~çç¥~~ { test: /\.(eot|woff2?|svg|ttf)([?]?.*)$/, use: 'null-loader', }, ]), }, plugins: options.plugins.concat([ new WebpackAssetsManifest({ publicPath: true }), // ~~çç¥~~ ]), devtool: options.devtool, target: 'web', // Make web variables accessible to webpack, e.g. window optimization: { namedModules: true, splitChunks: { cacheGroups: { style_commons: { test: /\.(sc|sa|c)ss$/, chunks: (chunk) => { // ignore patterns return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-presentation/); }, name: 'styles/style-commons', minSize: 1, priority: 30, enforce: true, }, // ~~çç¥~~ }, }, minimizer: options.optimization.minimizer || [], }, performance: options.performance || {}, stats: options.stats || {}, }; }; ããã¯å
±éã®ãã¡ã€ã«ã§ãããéçºçšã補åçšã®èšå®ããããŸãã ããã¯ãäžã®å
±éãã¡ã€ã«ã«ãããããã« options.module.rules.concat([ ãã®ããã«ããŠãå
±éã®ãã¡ã€ã«ãšéçºçšã補åçšã®èšå®ãããããããŒãžããŠããŸãã èšå®ã®è©³çްã«ã€ããŠã¿ãŠãããŸãããã mode mode: options.mode, // éçºçšã補åçšã§ãããã'development', 'production'ãèšå® ãã®é
ç®ã«èšå®ã§ããã®ã¯ã production , development , none ã®3ã€ã§ããproductionã¢ãŒãã§ã¯äžèŠãªè¡ãåé€ããããããã©ãŠã¶ãèªã¿èŸŒãã®ã«æé©åãããŠããã®ã§ãããã°ã«åããŠããŸããããªã®ã§GROWIã§ã¯ãããããéçºçšã§ã¯ development ãæ¬çªçšã§ã¯ production ãèšå®ããŠããŸãã entry entry: Object.assign({ 'js/boot': './src/client/boot', 'js/app': './src/client/app', // ~~çç¥~~ 'styles/theme-blackboard': './src/styles/theme/blackboard.scss', 'styles/style-hackmd': './src/styles-hackmd/style.scss', }, options.entry || {}), // Merge with env dependent settings ãã®é
ç®ã§ã¯èªã¿èŸŒã¿ãéå§ãããã¡ã€ã«ïŒãšã³ããªãŒãã€ã³ãïŒãæå®ããŸããçŸåšã¯ Objct ã§æå®ããŠããŸããã string ã string[] ãªã©ã§ãæå®ã§ããŸããå€§èŠæš¡ãªéçºã«ãªããšè€æ°ã®ãã¡ã€ã«ãèªã¿èŸŒãã§è€æ°ã®ãã³ãã«ãçæããããšãããã®ã§ããã®å Žåã¯ãšã³ããªãŒãã€ã³ãã«ãã£ã³ã¯åãã€ããããšã§åºåå
ã§ãã£ã³ã¯åãå©çšããããšãã§ããããããããããããšãã§ããŸãã output output: Object.assign({ path: path.resolve(__dirname, '../public'), publicPath: '/', filename: '[name].bundle.js', }, options.output || {}), // Merge with env dependent settings ãã®é
ç®ã§ã¯ãã³ãã«åããããã¡ã€ã«ã®åºåå
ãæå®ããŸããGROWIã§ã¯publicãã£ã¬ã¯ããªäžã« [name].bundle.js ãšãããã³ãã«ãã¡ã€ã«ãçæãããŸãããšã³ããªãŒãã€ã³ãèšå®ã®äžçªäžã®äŸã§ã¿ããšã js/boot ãšãããã£ã³ã¯åã§ ./src/client/boot ã®ãã¡ã€ã«ãæå®ãããŠããã®ã§ /js/boot.bundle.js ãšãããã¡ã€ã«ã public ãã£ã¬ã¯ããªäžã«çæãããŸããpublicPathã®é
ç®ã§ã¯ãoutputã«åºåããããã¡ã€ã«ãåç
§ãããå
ãæå®ããŸããGROWIã®èšå®ã®å Žåãåºåããããã¡ã€ã«ã¯ / ã«ãŒããã£ã¬ã¯ããªããåç
§ãããŸãã external externals: { jquery: 'jQuery', emojione: 'emojione', hljs: 'hljs', 'dtrace-provider': 'dtrace-provider', }, ãã®é
ç®ã§ã¯å€éšäŸåã®ãŸãŸã«ãããããããã³ãã«å¯Ÿè±¡ããå€ããã®ãèšå®ããŠããŸããGROWIã§ã¯jQueryãemojioneãªã©ããã®é
ç®ã«èšå®ããŠãå€éšäŸåã«ããŠããŸããããã«èšå®ããã«scriptã§CDNèªã¿èŸŒã¿ãããŠããŠã import ããŠäœ¿çšããŠãããšwebpackã¯ã¢ãžã¥ãŒã«è§£æ±ºã§ããã«ãšã©ãŒãåºãŠããŸããŸãã resolve resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], plugins: [ new TsconfigPathsPlugin({ configFile: path.resolve(__dirname, '../tsconfig.build.client.json'), extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], }), ], }, ãã®é
ç®ã§ã¯ã¢ãžã¥ãŒã«ããã³ãã«åãããéã®èšå®ãããããšãã§ããŸãã extensions ããã§ã¯ãšã³ããªãŒãã€ã³ãã®ãã¡ã€ã«æ¡åŒµåãå®çŸ©ããŸããwebpackã¯ãã¡ã€ã«ãåŠçãããšããããã«å®çŸ©ãããæ¡åŒµåãé
åã®å
é ããèŠãŠãããåœãŠã¯ãŸã£ããšãã«åŠçãéå§ããŸãã plugins ããã§ã¯ã¢ãžã¥ãŒã«ããã³ãã«åããéã«äœ¿çšãããã©ã°ã€ã³ãå®çŸ©ããŸããGROWIã§ã¯ TsconfigPathsPlugin ãšãããã©ã°ã€ã³ãçšããŠããŸãã ts-loader ãçšããŠJSã®ãã©ã³ã¹ãã€ã«ããããšãã tsconfig.json ã® baseUrl , paths ãçšããŠããå Žåããã®ãã©ã°ã€ã³ãå
¥ããªããšpathsã®ãšã€ãªã¢ã¹ãwebpackãå©çšã§ããŸããã node node: { fs: 'empty', }, ãã®é
ç®ã§ã¯nodeã«ãããã¢ãžã¥ãŒã«ã«å¯ŸããŠãããªãã£ã«ãè¡ã£ãããã¢ãã¯ãå
¥ãããããããšãèšå®ããŸããnodeã®ã¢ãžã¥ãŒã«ã¯ãã©ãŠã¶ããã¯å©çšã§ããªããããé©åã«èšå®ãããªããŸãŸå®è¡ãããšã fs ããªããããšæãããŠããŸããŸãããªã®ã§GROWIã§ã¯ empty ãèšå®ããããšã§ fs ã«ç©ºã®ãªããžã§ã¯ããããããšã©ãŒãåé¿ããŠããŸãã https://stackoverflow.com/questions/39249237/node-cannot-find-module-fs-when-using-webpack äžã®ãªã³ã¯ã§ãè°è«ãããŠããŸãããããã¯å°ãå€ãã®è§£æ±ºçã«ãªã£ãŠããŸãã module module: { rules: options.module.rules.concat([ { test: /.(jsx?|tsx?)$/, exclude: { test: /node_modules/, exclude: [ // include as a result /node_modules\/codemirror/, ], }, use: [{ loader: 'ts-loader', options: { transpileOnly: true, configFile: path.resolve(__dirname, '../tsconfig.build.client.json'), }, }], }, { test: /locales/, loader: '@alienfast/i18next-loader', options: { basenameAsNamespace: true, }, }, /* * File loader for supporting images, for example, in CSS files. */ { test: /\.(jpg|png|gif)$/, use: 'file-loader', }, /* File loader for supporting fonts, for example, in CSS files. */ { test: /\.(eot|woff2?|svg|ttf)([?]?.*)$/, use: 'null-loader', }, ]), }, ãã®é
ç®ã§ã¯ããããã®ã¢ãžã¥ãŒã«ãã©ã®ããã«ãã³ãã«åããããã®è©³çްãèšå®ããŸãã rules å
¬åŒããã¥ã¡ã³ãã«ãããšã rule ã«ã¯3ã€ã®ããŒããããã Conditions , Results , nested Rules ãšãããŠããŸãããããããè§£éãããªãã æ¡ä»¶ ã åŠç ã ãã¹ããããæ¡ä»¶ ãšãªãããªãšæããŸãããã®é
ç®ã§ã¯åºæ¬çã«ããããªæ¡ä»¶ã®æã¯ãããã£ãåŠçãããããšããèšå®ãããŠããŸããäžçªäžã®äŸãèŠããšãæ£èŠè¡šçŸã§ js, jsx, ts, tsx ãæå®ããŠã exclude ã§ node_modules é
äžã®ãã¡ã€ã«ã¯é€å€ããããšãèšå®ããŠããŸããããã«ããã®äžã§ã exclude ã§ /node_modules/codemirror/ ãããŠããã®ã§ node_modules é
äžã®ãã¡ codemirror ã®ã¿ãå«ããŸãããããŸã§ãruleã®ãã¡ãæ¡ä»¶ã§ãããããŠãããã®æ¡ä»¶ã«åèŽãããã¡ã€ã«ã use ã§æå®ããloaderã§åŠçããŸããä»å㯠ts-loader ã§åŠçããŠããã option ã§ transpileOnly ã true ã«ããããšã§åã®ãã§ãã¯ãåå®çŸ©ãã¡ã€ã«ã®åºåãçç¥ããããšã§ã³ã³ãã€ã«ã«ãããæéãå°ãªãããŠããŸããããã§åã®ãã§ãã¯ãè¡ããªã代ããã«ãGROWIã§ã¯CIã§ tsc ãå®è¡ãããã§ãã¯ããŠããŸãã 1çªäžã®èšå®ã§ã¯äžèšã®ãããªããšãè¡ã£ãŠããŸãã plugins plugins: options.plugins.concat([ new WebpackAssetsManifest({ publicPath: true }), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }), // ignore new webpack.IgnorePlugin(/^\.\/lib\/deflate\.js/, /markdown-it-plantuml/), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new LodashModuleReplacementPlugin({ flattening: true, }), new webpack.ProvidePlugin({ // refs externals jQuery: 'jquery', $: 'jquery', }), ]), ããã§ã¯loaderã§ã¯æäŸã§ããªãè¿œå æ©èœãæäŸãããã©ã°ã€ã³ãèšå®ããŸãã WebpackAssetsManifest ãã®ãã©ã°ã€ã³ã§ã¯å
ã®ãã¡ã€ã«åãšããã·ã¥åããããã¡ã€ã«åã察å¿ãããããã®JSONãã¡ã€ã«ãçæããŸãã webpack.DefinePlugin ãã®ãã©ã°ã€ã³ã§ã¯ã³ã³ãã€ã«æã«èšå®ãããã°ããŒãã«ãªå®æ°ãèšå®ããããšãã§ããŸãã webpack.IgnorePlugin ãã®ãã©ã°ã€ã³ã§ã¯ãã³ãã«åãããéã«ç¡èŠãããã®ãå®çŸ©ããŸããäŸãã°ãlocaleãã¡ã€ã«ãªã©ã¯ããã§èšå®ããããšã§äœ¿çšããªã倧éšåã®ãã¡ã€ã«ããã³ãã«æã«ç¡èŠããããšãã§ããŸãã LodashModuleReplacementPlugin ãã®ãã©ã°ã€ã³ã§ã¯loadshã®äžã§äœ¿ãããŠãããã®ã®ã¿ããã³ãã«åããŠãã³ãã«ãã¡ã€ã«ãè¥å€§åããã®ãé²ãã§ããŸãã webpack.ProvidePlugin ãã®ãã©ã°ã€ã³ã§ã¯ã¢ãžã¥ãŒã«ã import ã require ã§èªã¿èŸŒãŸãªããŠèªåçã«èªã¿èŸŒãæ©èœãæäŸããŸãã devtool devtool: options.devtool, // éçºæã®ã¿'cheap-module-eval-source-map'ãæå® ãã®é
ç®ã§ã¯ããœãŒã¹ããããçæããããŸãã©ã®ããã«çæããããèšå®ããŠããŸãããœãŒã¹ãããã«ãã£ãŠãã³ãã«åããããã¡ã€ã«ãšå
ã®ãã¡ã€ã«ã®é¢é£ããããã®ã§ãããã°ããããããªããããéçºæã«ã¯ãšãŠã䟿å©ã§ãããã®ãªãã·ã§ã³ã«ã¯ããããã®çš®é¡ãããã®ã§ãããGROWIã§ã¯ts-loaderã§ãµããŒããããŠãã cheap-module-eval-source-map ã䜿çšããŠããŸãã補åçã®èšå®ã§ã¯ãã®é
ç®ã¯äžæžããããŠããŸãã target target: 'web', ãã®é
ç®ã§ã¯ãçæãããã³ãã«ãã¡ã€ã«ã®ã¿ãŒã²ãããèšå®ããŸããäŸãã°Node.jsã®ç°å¢ã§requireã䜿ã£ãŠãã³ãã«ãã¡ã€ã«ãèªã¿èŸŒãå Žåã¯ããã§ node ãèšå®ãããããããã§ãããä»åã®ç®çã¯ãã©ãŠã¶ã§HTMLããèªã¿èŸŒãããšãªã®ã§ web ãæå®ããŸãã optimization optimization: { namedModules: true, splitChunks: { cacheGroups: { style_commons: { test: /\.(sc|sa|c)ss$/, chunks: (chunk) => { // ignore patterns return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-presentation/); }, name: 'styles/style-commons', minSize: 1, priority: 30, enforce: true, }, commons: { test: /(src|resource)[\\/].*\.(js|jsx|json)$/, chunks: (chunk) => { // ignore patterns return chunk.name != null && !chunk.name.match(/boot/); }, name: 'js/commons', minChunks: 2, minSize: 1, priority: 20, }, vendors: { test: /node_modules[\\/].*\.(js|jsx|json)$/, chunks: (chunk) => { // ignore patterns return chunk.name != null && !chunk.name.match(/boot|legacy-presentation|hackmd-/); }, name: 'js/vendors', minSize: 1, priority: 10, enforce: true, }, }, }, minimizer: options.optimization.minimizer || [], }, ãã®é
ç®ã§ã¯webpackãå®è¡ãããšãã®æ§ã
ãªæé©åã«ã€ããŠã®èšå®ãããŠããŸãã namedModule ãã®é
ç®ã« true ãèšå®ããããšã§ã¢ãžã¥ãŒã«ã«ååãèšå®ããããããã°ããããããªããŸãã splitChunks ãã®é
ç®ã§ã¯ãè€æ°ã®ãšã³ããªãŒãã€ã³ãéã§å©çšããŠããå
±éã¢ãžã¥ãŒã«ããã³ãã«ãããã¡ã€ã«ããåºåããããã®èšå®ãããŸãã splitChunks ã« cacheGroups ãèšå®ããããšã§å
±éåãããã³ãã«ãã¡ã€ã«ãã°ã«ãŒãåããããšãã§ããŸãã 1ã€ç®ã®èšå®ãäŸã«ã¿ããš scss, sass, css ã®ãã¡ã€ã«ãè€æ°ã®ã¢ãžã¥ãŒã«ããå©çšãããŠããå Žåããã£ã³ã¯ã®ååãnullã§ãªããååã«ãstyle-, theme-, legacy-presentationããå«ãŸããªãå Žåã styles-style-commons ã«ãã³ãã«ãã¡ã€ã«ãçæãããŸããããã« minSize ã§ ïŒ ãèšå®ãããŠããã®ã§1byteæªæºã®ã¢ãžã¥ãŒã«ã¯è€æ°ã®ã¢ãžã¥ãŒã«ã§å©çšãããŠããŠãå
±éåã¯ããªããããªèšå®ã«ãªã£ãŠããŸãããŸãã priority ãèšå®ãããŠããã®ã§ãã£ã³ã¯ãè€æ°ã® cacheGroup ã«ãŸããã£ãŠãããšãã«ã¯ priority ãé«ãã°ã«ãŒããåªå
ãããŸãã minimizer ãã®é
ç®ã«å€ããããããšã§å®è¡æã«å©çšããããã©ã«ãã®minimizerãäžæžãããããšãã§ããŸãã performance ãã®é
ç®ã§ã¯webpackã®æ§ã
ãªæåãèšå®ããŸããGROWIã§ã¯éçºæã« hints ã false ã«ããããšã§webpackãäœãå€åãæ€ç¥ããŠãèŠåãæ³šæãåºããªãèšå®ã«ããŠããŸãã ãŸãšã 以äžãGROWIã«ãããwebpackã®èšå®ã§ããæåã«ãæžããããã«ãçæ³ã¯ãããªè€éãªèšå®ãæžããªããŠãã ãã§ãã¢ãžã¥ãŒã«ãã³ãã©ãŒã®èšå®ãã§ããããšã§ããããããããwebpackã¯ãŸã ãŸã 䜿ãããŠãããããwebpackã®èšå®ãç¥ã£ãŠããªããšå°ãå Žé¢ããããããããŸãããä»åã¯ãããªwebpackã®èšå®ã«ã€ããŠèª¿ã¹ãŠã¿ããšããèšäºã§ããã