webpack.common.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /* eslint-disable camelcase */
  2. /**
  3. * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. const { VueLoaderPlugin } = require('vue-loader')
  7. const path = require('path')
  8. const BabelLoaderExcludeNodeModulesExcept = require('babel-loader-exclude-node-modules-except')
  9. const webpack = require('webpack')
  10. const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
  11. const WorkboxPlugin = require('workbox-webpack-plugin')
  12. const modules = require('./webpack.modules.js')
  13. const { readFileSync } = require('fs')
  14. const appVersion = readFileSync('./version.php').toString().match(/OC_VersionString[^']+'([^']+)/)?.[1] ?? 'unknown'
  15. const formatOutputFromModules = (modules) => {
  16. // merge all configs into one object, and use AppID to generate the fileNames
  17. // with the following format:
  18. // AppId-fileName: path/to/js-file.js
  19. const moduleEntries = Object.keys(modules).map(moduleKey => {
  20. const module = modules[moduleKey]
  21. const entries = Object.keys(module).map(entryKey => {
  22. const entry = module[entryKey]
  23. return { [`${moduleKey}-${entryKey}`]: entry }
  24. })
  25. return Object.assign({}, ...Object.values(entries))
  26. })
  27. return Object.assign({}, ...Object.values(moduleEntries))
  28. }
  29. const modulesToBuild = () => {
  30. const MODULE = process?.env?.MODULE
  31. if (MODULE) {
  32. if (!modules[MODULE]) {
  33. throw new Error(`No module "${MODULE}" found`)
  34. }
  35. return formatOutputFromModules({
  36. [MODULE]: modules[MODULE],
  37. })
  38. }
  39. return formatOutputFromModules(modules)
  40. }
  41. module.exports = {
  42. entry: modulesToBuild(),
  43. output: {
  44. // Step away from the src folder and extract to the js folder
  45. path: path.join(__dirname, 'dist'),
  46. // Let webpack determine automatically where it's located
  47. publicPath: 'auto',
  48. filename: '[name].js?v=[contenthash]',
  49. chunkFilename: '[name]-[id].js?v=[contenthash]',
  50. // Make sure sourcemaps have a proper path and do not
  51. // leak local paths https://github.com/webpack/webpack/issues/3603
  52. devtoolNamespace: 'nextcloud',
  53. devtoolModuleFilenameTemplate(info) {
  54. const rootDir = process?.cwd()
  55. const rel = path.relative(rootDir, info.absoluteResourcePath)
  56. return `webpack:///nextcloud/${rel}`
  57. },
  58. clean: {
  59. keep: /icons\.css/, // Keep static icons css
  60. },
  61. },
  62. module: {
  63. rules: [
  64. {
  65. test: /davclient/,
  66. loader: 'exports-loader',
  67. options: {
  68. type: 'commonjs',
  69. exports: 'dav',
  70. },
  71. },
  72. {
  73. test: /\.css$/,
  74. use: ['style-loader', 'css-loader'],
  75. },
  76. {
  77. test: /\.scss$/,
  78. use: ['style-loader', 'css-loader', 'sass-loader'],
  79. },
  80. {
  81. test: /\.vue$/,
  82. loader: 'vue-loader',
  83. exclude: BabelLoaderExcludeNodeModulesExcept([
  84. 'vue-material-design-icons',
  85. 'emoji-mart-vue-fast',
  86. ]),
  87. },
  88. {
  89. test: /\.tsx?$/,
  90. use: [
  91. 'babel-loader',
  92. {
  93. // Fix TypeScript syntax errors in Vue
  94. loader: 'ts-loader',
  95. options: {
  96. transpileOnly: true,
  97. },
  98. },
  99. ],
  100. exclude: BabelLoaderExcludeNodeModulesExcept([]),
  101. },
  102. {
  103. test: /\.js$/,
  104. loader: 'babel-loader',
  105. // automatically detect necessary packages to
  106. // transpile in the node_modules folder
  107. exclude: BabelLoaderExcludeNodeModulesExcept([
  108. '@nextcloud/dialogs',
  109. '@nextcloud/event-bus',
  110. 'davclient.js',
  111. 'nextcloud-vue-collections',
  112. 'p-finally',
  113. 'p-limit',
  114. 'p-locate',
  115. 'p-queue',
  116. 'p-timeout',
  117. 'p-try',
  118. 'semver',
  119. 'striptags',
  120. 'toastify-js',
  121. 'v-tooltip',
  122. 'yocto-queue',
  123. ]),
  124. },
  125. {
  126. test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf)$/,
  127. type: 'asset/inline',
  128. },
  129. {
  130. test: /\.handlebars/,
  131. loader: 'handlebars-loader',
  132. },
  133. {
  134. resourceQuery: /raw/,
  135. type: 'asset/source',
  136. },
  137. ],
  138. },
  139. optimization: {
  140. minimizer: [{
  141. apply: (compiler) => {
  142. // Lazy load the Terser plugin
  143. const TerserPlugin = require('terser-webpack-plugin')
  144. new TerserPlugin({
  145. extractComments: {
  146. condition: /^\**!|@license|@copyright|SPDX-License-Identifier|SPDX-FileCopyrightText/i,
  147. filename: (fileData) => {
  148. // The "fileData" argument contains object with "filename", "basename", "query" and "hash"
  149. return `${fileData.filename}.license${fileData.query}`
  150. },
  151. },
  152. terserOptions: {
  153. compress: {
  154. passes: 2,
  155. },
  156. },
  157. }).apply(compiler)
  158. },
  159. }],
  160. splitChunks: {
  161. automaticNameDelimiter: '-',
  162. minChunks: 3, // minimum number of chunks that must share the module
  163. cacheGroups: {
  164. vendors: {
  165. // split every dependency into one bundle
  166. test: /[\\/]node_modules[\\/]/,
  167. // necessary to keep this name to properly inject it
  168. // see OC_Template.php
  169. name: 'core-common',
  170. chunks: 'all',
  171. },
  172. },
  173. },
  174. },
  175. plugins: [
  176. new VueLoaderPlugin(),
  177. new NodePolyfillPlugin(),
  178. new webpack.ProvidePlugin({
  179. // Provide jQuery to jquery plugins as some are loaded before $ is exposed globally.
  180. // We need to provide the path to node_moduels as otherwise npm link will fail due
  181. // to tribute.js checking for jQuery in @nextcloud/vue
  182. jQuery: path.resolve(path.join(__dirname, 'node_modules/jquery')),
  183. }),
  184. new WorkboxPlugin.GenerateSW({
  185. swDest: 'preview-service-worker.js',
  186. clientsClaim: true,
  187. skipWaiting: true,
  188. exclude: [/.*/], // don't do pre-caching
  189. inlineWorkboxRuntime: true,
  190. sourcemap: false,
  191. // Increase perfs with less logging
  192. disableDevLogs: true,
  193. // Define runtime caching rules.
  194. runtimeCaching: [{
  195. // Match any preview file request
  196. // /apps/files_trashbin/preview?fileId=156380&a=1
  197. // /core/preview?fileId=155842&a=1
  198. urlPattern: /^.*\/(apps|core)(\/[a-z-_]+)?\/preview.*/i,
  199. // Apply a strategy.
  200. handler: 'CacheFirst',
  201. options: {
  202. // Use a custom cache name.
  203. cacheName: 'previews',
  204. // Only cache 10000 images.
  205. expiration: {
  206. maxAgeSeconds: 3600 * 24 * 7, // one week
  207. maxEntries: 10000,
  208. },
  209. },
  210. }],
  211. }),
  212. // Make appName & appVersion available as a constants for '@nextcloud/vue' components
  213. new webpack.DefinePlugin({ appName: JSON.stringify('Nextcloud') }),
  214. new webpack.DefinePlugin({ appVersion: JSON.stringify(appVersion) }),
  215. // @nextcloud/moment since v1.3.0 uses `moment/min/moment-with-locales.js`
  216. // Which works only in Node.js and is not compatible with Webpack bundling
  217. // It has an unused function `localLocale` that requires locales by invalid relative path `./locale`
  218. // Though it is not used, Webpack tries to resolve it with `require.context` and fails
  219. new webpack.IgnorePlugin({
  220. resourceRegExp: /^\.\/locale$/,
  221. contextRegExp: /moment\/min$/,
  222. }),
  223. ],
  224. externals: {
  225. OC: 'OC',
  226. OCA: 'OCA',
  227. OCP: 'OCP',
  228. },
  229. resolve: {
  230. alias: {
  231. // make sure to use the handlebar runtime when importing
  232. handlebars: 'handlebars/runtime',
  233. vue$: path.resolve('./node_modules/vue'),
  234. },
  235. extensions: ['*', '.ts', '.js', '.vue'],
  236. extensionAlias: {
  237. /**
  238. * Resolve TypeScript files when using fully-specified esm import paths
  239. * https://github.com/webpack/webpack/issues/13252
  240. */
  241. '.js': ['.js', '.ts'],
  242. },
  243. symlinks: true,
  244. fallback: {
  245. fs: false,
  246. },
  247. },
  248. }