webpack-base.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const { resolve } = require('path');
  2. const { VueLoaderPlugin } = require('vue-loader');
  3. const HtmlWebpackPlugin = require('html-webpack-plugin');
  4. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  5. const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
  6. const TerserPlugin = require('terser-webpack-plugin');
  7. const deepmerge = require('deepmerge');
  8. const { alias, extensions, isProd } = require('./common');
  9. const defaultHtmlOptions = {
  10. minify: isProd && {
  11. collapseWhitespace: true,
  12. removeAttributeQuotes: true,
  13. removeComments: true,
  14. removeOptionalTags: true,
  15. removeRedundantAttributes: true,
  16. removeScriptTypeAttributes: true,
  17. removeStyleLinkTypeAttributes: true,
  18. },
  19. meta: { viewport: 'width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0' },
  20. css: [],
  21. js: [],
  22. };
  23. const MIN_OPTS = {
  24. extractComments: false,
  25. parallel: true,
  26. terserOptions: {
  27. compress: {
  28. // `terser` often inlines big one-time functions inside a small "hot" function
  29. reduce_funcs: false,
  30. },
  31. output: {
  32. ascii_only: true,
  33. comments: false,
  34. wrap_func_args: false, // disabling a premature optimization designed for old browsers
  35. },
  36. },
  37. };
  38. const MIN_OPTS_PUBLIC = isProd && {
  39. include: 'public/',
  40. ...MIN_OPTS,
  41. };
  42. const MIN_OPTS_MAIN = isProd && deepmerge.all([{}, MIN_OPTS, {
  43. exclude: 'public/',
  44. terserOptions: {
  45. compress: {
  46. ecma: 8, // ES2017 Object.entries and so on
  47. passes: 2, // necessary now since we removed plaid's minimizer
  48. unsafe_arrows: true, // it's 'safe' since we don't rely on function prototypes
  49. },
  50. },
  51. }]);
  52. const nodeModules = resolve('node_modules');
  53. const pages = [
  54. 'background',
  55. 'confirm',
  56. 'options',
  57. 'popup',
  58. ];
  59. const createHtmlPage = key => new HtmlWebpackPlugin({
  60. ...defaultHtmlOptions,
  61. filename: `${key}/index.html`,
  62. chunks: [`${key}/index`],
  63. title: 'Violentmonkey',
  64. inject: 'body',
  65. scriptLoading: 'blocking', // we don't need `defer` and it breaks in some browsers, see #1632
  66. });
  67. const splitVendor = prefix => ({
  68. [prefix]: {
  69. test: new RegExp(`node_modules[/\\\\]${prefix}`),
  70. name: `public/lib/${prefix}`,
  71. chunks: 'all',
  72. priority: 100,
  73. },
  74. });
  75. function styleLoader(options) {
  76. const {
  77. extract,
  78. loaders = [],
  79. fallback = 'style-loader',
  80. modules = false,
  81. } = options || {};
  82. const cssLoader = {
  83. loader: 'css-loader',
  84. options: {
  85. modules,
  86. importLoaders: 1,
  87. sourceMap: false,
  88. },
  89. };
  90. return [
  91. extract ? MiniCssExtractPlugin.loader : fallback,
  92. cssLoader,
  93. ...loaders,
  94. ];
  95. }
  96. function styleRule(options, rule) {
  97. return {
  98. test: /\.css$/,
  99. use: styleLoader(options),
  100. ...rule,
  101. };
  102. }
  103. const styleOptions = {
  104. extract: isProd,
  105. };
  106. const postcssLoader = {
  107. loader: 'postcss-loader',
  108. };
  109. const getBaseConfig = () => ({
  110. mode: isProd ? 'production' : 'development',
  111. target: 'web', // required by live reloading
  112. devtool: isProd ? false : 'inline-source-map',
  113. output: {
  114. path: resolve('dist'),
  115. publicPath: '/',
  116. filename: '[name].js',
  117. hashFunction: 'xxhash64',
  118. },
  119. node: {
  120. global: false,
  121. },
  122. performance: {
  123. maxEntrypointSize: 1e6,
  124. maxAssetSize: 0.5e6,
  125. },
  126. resolve: {
  127. alias,
  128. extensions,
  129. },
  130. module: {
  131. rules: [
  132. // JS/TS
  133. {
  134. test: /\.m?[jt]sx?$/,
  135. use: 'babel-loader',
  136. exclude: file => /node_modules/.test(file) && !/vueleton|@vue[/\\]shared/.test(file),
  137. },
  138. // CSS
  139. {
  140. oneOf: [
  141. // library CSS files: node_modules/**/*.css
  142. styleRule(styleOptions, {
  143. include: [nodeModules],
  144. }),
  145. // CSS modules: src/**/*.module.css
  146. styleRule({
  147. ...styleOptions,
  148. loaders: [postcssLoader],
  149. modules: {},
  150. }, {
  151. test: /\.module\.css$/,
  152. }),
  153. // normal CSS files: src/**/*.css
  154. styleRule({
  155. ...styleOptions,
  156. loaders: [postcssLoader],
  157. }),
  158. ],
  159. },
  160. // SVG
  161. {
  162. test: /\.svg$/,
  163. use: [{
  164. loader: 'svg-sprite-loader',
  165. options: {
  166. // extract: extractSVG,
  167. },
  168. }],
  169. include: [resolve('src/resources/svg')],
  170. },
  171. // Vue
  172. {
  173. test: /\.vue$/,
  174. loader: 'vue-loader',
  175. options: {
  176. babelParserPlugins: ['functionBind'],
  177. compilerOptions: {
  178. whitespace: 'condense',
  179. },
  180. },
  181. },
  182. ],
  183. },
  184. optimization: {
  185. runtimeChunk: false,
  186. splitChunks: {
  187. cacheGroups: {
  188. 'common-ui': {
  189. name: 'common-ui',
  190. test: new RegExp([
  191. /\bsvg/,
  192. // don't extract CSS as it'll change the relative order of rules which breaks appearance
  193. 'src/common/(?!zip|.*\\.css$)',
  194. 'node_modules/@violentmonkey/shortcut',
  195. 'node_modules/@?vue',
  196. ].map(re => re.source || re).join('|').replace(/\\?\//g, '[/\\\\]')),
  197. chunks: c => ![
  198. 'background/index', // only 4kB of common code
  199. 'injected',
  200. 'injected-web',
  201. ].includes(c.name),
  202. },
  203. ...splitVendor('codemirror'),
  204. },
  205. },
  206. minimizer: isProd ? [
  207. new CssMinimizerPlugin(),
  208. new TerserPlugin(MIN_OPTS_PUBLIC),
  209. new TerserPlugin(MIN_OPTS_MAIN),
  210. ] : [],
  211. },
  212. plugins: [
  213. new VueLoaderPlugin(),
  214. ...styleOptions.extract ? [new MiniCssExtractPlugin({
  215. filename: '[name].css',
  216. })] : [],
  217. ],
  218. });
  219. const getPageConfig = () => {
  220. const config = getBaseConfig();
  221. config.entry = Object.fromEntries(pages.map(name => [`${name}/index`, `./src/${name}`]));
  222. config.plugins = [
  223. ...config.plugins,
  224. ...pages.filter(key => key !== 'background').map(createHtmlPage),
  225. ];
  226. return config;
  227. };
  228. exports.isProd = isProd;
  229. exports.getBaseConfig = getBaseConfig;
  230. exports.getPageConfig = getPageConfig;