shakapacker 8.1.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50df828db39af7e84673e9ab8bd4569d644084e428902d02084574b77db9bd8e
4
- data.tar.gz: 6aac530ddb579fc7f92888a1e65282bbf69c98bc2773fcd27d77ec19671ec74d
3
+ metadata.gz: 85c3a527653e307cedc881f7879b245feaca4d79b47f7f7abac358f38a1e8305
4
+ data.tar.gz: eae6e26f151f0e6d346f9cef659854b91b7e0a4a3f388aa5cef016d6323f2c52
5
5
  SHA512:
6
- metadata.gz: e6250e22762bd47aaf6dc0d2b93354bea65e3ca2e5d629177bec478495de3e6eaba5a458e3f0feda712bf08bf2b48f2f0a6f003baa6b2ffb354e49fb042e65d3
7
- data.tar.gz: 45f445078b0d50f1eebc6d27f8a76ceeb3c8eb38cce4a4881562ab77d3a47f6e86d293f5c46a468a8fc4566bc217b39105cec85a78b5fdc1e77112c22a1d3ff8
6
+ metadata.gz: b04c921e57bb1fe74f9811d5e7cc9ba137600f9da55b906db3d51cc754533c7ed62f69bf9800172e07a2c4676a61bb13e0b939e12f25322c3065060a8c46d015
7
+ data.tar.gz: 870d7895ec05e62183f8958bae438b417b490df5c22d30f656a818d3e9c2747b8a16adb2f973bb0a6a36ac497ce4e725a7d2b139ece7ef1e96ac4171285673da
data/CHANGELOG.md CHANGED
@@ -4,12 +4,30 @@
4
4
  * Please see the [v6 Upgrade Guide](./docs/v6_upgrade.md) to go from versions prior to v6.
5
5
  * [ShakaCode](https://www.shakacode.com) offers support for upgrading from Webpacker or using Shakapacker. If interested, contact Justin Gordon, [justin@shakacode.com](mailto:justin@shakacode.com).
6
6
 
7
- _next_ branch is for v8 changes
8
-
9
- ## Versions
7
+ # Versions
10
8
  ## [Unreleased]
11
9
  Changes since the last non-beta release.
12
10
 
11
+ ## [v8.3.0] - April 25, 2025
12
+ ### Added
13
+
14
+ - Allow `webpack-assets-manifest` v6. [PR 562](https://github.com/shakacode/shakapacker/pull/562) by [tagliala](https://github.com/tagliala), [shoeyn](https://github.com/shoeyn).
15
+
16
+ ### Changed
17
+
18
+ - Instead of a fixed `core-js` version, take the current one from `node_modules` if available. [PR 556](https://github.com/shakacode/shakapacker/pull/556) by [alexeyr-ci2](https://github.com/alexeyr-ci2).
19
+ - Require webpack >= 5.76.0 to reduce exposure to CVE-2023-28154. [PR 568](https://github.com/shakacode/shakapacker/pull/568) by [granowski](https://github.com/granowski).
20
+
21
+ ### Fixed
22
+
23
+ - More precise types for `devServer` and `rules` in the configuration. [PR 555](https://github.com/shakacode/shakapacker/pull/555) by [alexeyr-ci2](https://github.com/alexeyr-ci2).
24
+
25
+ ## [v8.2.0] - March 12, 2025
26
+ ### Added
27
+
28
+ - Support for `async` attribute in `javascript_pack_tag`, `append_javascript_pack_tag`, and `prepend_javascript_pack_tag`. [PR 554](https://github.com/shakacode/shakapacker/pull/554) by [AbanoubGhadban](https://github.com/abanoubghadban).
29
+ - Allow `babel-loader` v10. [PR 552](https://github.com/shakacode/shakapacker/pull/552) by [shoeyn](https://github.com/shoeyn).
30
+
13
31
  ## [v8.1.0] - January 20, 2025
14
32
 
15
33
  ### Added
@@ -401,7 +419,9 @@ Note: [Rubygem is 6.3.0.pre.rc.1](https://rubygems.org/gems/shakapacker/versions
401
419
  ## v5.4.3 and prior changes from rails/webpacker
402
420
  See [CHANGELOG.md in rails/webpacker (up to v5.4.3)](https://github.com/rails/webpacker/blob/master/CHANGELOG.md)
403
421
 
404
- [Unreleased]: https://github.com/shakacode/shakapacker/compare/v8.1.0...main
422
+ [Unreleased]: https://github.com/shakacode/shakapacker/compare/v8.3.0...main
423
+ [v8.3.0]: https://github.com/shakacode/shakapacker/compare/v8.2.0...v8.3.0
424
+ [v8.2.0]: https://github.com/shakacode/shakapacker/compare/v8.1.0...v8.2.0
405
425
  [v8.1.0]: https://github.com/shakacode/shakapacker/compare/v8.0.2...v8.1.0
406
426
  [v8.0.2]: https://github.com/shakacode/shakapacker/compare/v8.0.1...v8.0.2
407
427
  [v8.0.1]: https://github.com/shakacode/shakapacker/compare/v8.0.0...v8.0.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shakapacker (8.1.0)
4
+ shakapacker (8.3.0)
5
5
  activesupport (>= 5.2)
6
6
  package_json
7
7
  rack-proxy (>= 0.6.1)
data/README.md CHANGED
@@ -275,7 +275,7 @@ You can provide multiple packs and other attributes. Note, `defer` defaults to s
275
275
  ```
276
276
 
277
277
  The resulting HTML would look like this:
278
- ```
278
+ ```html
279
279
  <script src="/packs/vendor-16838bab065ae1e314.js" data-turbo-track="reload" defer></script>
280
280
  <script src="/packs/calendar~runtime-16838bab065ae1e314.js" data-turbo-track="reload" defer></script>
281
281
  <script src="/packs/calendar-1016838bab065ae1e314.js" data-turbo-track="reload" defer"></script>
@@ -287,6 +287,26 @@ In this output, both the calendar and map codes might refer to other common libr
287
287
 
288
288
  Note, the default of "defer" for the `javascript_pack_tag`. You can override that to `false`. If you expose jquery globally with `expose-loader,` by using `import $ from "expose-loader?exposes=$,jQuery!jquery"` in your `app/javascript/application.js`, pass the option `defer: false` to your `javascript_pack_tag`.
289
289
 
290
+ The `javascript_pack_tag` also supports the `async` attribute, which you can enable by passing `async: true`:
291
+
292
+ ```erb
293
+ <%= javascript_pack_tag 'application', async: true %>
294
+ ```
295
+
296
+ This will generate script tags with the `async` attribute, which allows the browser to download and execute the script asynchronously without blocking HTML parsing:
297
+
298
+ ```html
299
+ <script src="/packs/vendor-16838bab065ae1e314.js" async></script>
300
+ <script src="/packs/application~runtime-16838bab065ae1e314.js" async></script>
301
+ <script src="/packs/application-1016838bab065ae1e314.js" async></script>
302
+ ```
303
+
304
+ Note that when using `async: true`, scripts may execute in any order as soon as they're downloaded, which could cause issues if your code has dependencies between files. In most cases, `defer` (the default) is preferred as it maintains execution order.
305
+
306
+ > [!NOTE]
307
+ >
308
+ > When both `async` and `defer` attributes are specified, `async` takes precedence according to HTML5 specifications. So if you pass both `async: true` and `defer: true`, the script tag will use `async`.
309
+
290
310
  **Important:** Pass all your pack names as multiple arguments, not multiple calls, when using `javascript_pack_tag` and the `stylesheet_pack_tag`. Otherwise, you will get duplicated chunks on the page.
291
311
 
292
312
  ```erb
@@ -7,3 +7,4 @@ gem "rake", ">= 11.1"
7
7
  gem "rack-proxy", require: false
8
8
  gem "rspec-rails", "~> 5.0.0"
9
9
  gem "byebug"
10
+ gem "concurrent-ruby", "1.3.4"
@@ -9,3 +9,4 @@ gem "rake", ">= 11.1"
9
9
  gem "rack-proxy", require: false
10
10
  gem "rspec-rails", "~> 6.0.0"
11
11
  gem "byebug"
12
+ gem "concurrent-ruby", "1.3.4"
@@ -9,3 +9,4 @@ gem "rake", ">= 11.1"
9
9
  gem "rack-proxy", require: false
10
10
  gem "rspec-rails", "~> 7.0"
11
11
  gem "byebug"
12
+ gem "concurrent-ruby", "1.3.4"
@@ -95,22 +95,25 @@ module Shakapacker::Helper
95
95
  #
96
96
  # <%= javascript_pack_tag 'calendar' %>
97
97
  # <%= javascript_pack_tag 'map' %>
98
- def javascript_pack_tag(*names, defer: true, **options)
98
+ def javascript_pack_tag(*names, defer: true, async: false, **options)
99
99
  if @javascript_pack_tag_loaded
100
100
  raise "To prevent duplicated chunks on the page, you should call javascript_pack_tag only once on the page. " \
101
101
  "Please refer to https://github.com/shakacode/shakapacker/blob/main/README.md#view-helpers-javascript_pack_tag-and-stylesheet_pack_tag for the usage guide"
102
102
  end
103
103
 
104
- append_javascript_pack_tag(*names, defer: defer)
105
- non_deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:non_deferred], type: :javascript)
106
- deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:deferred], type: :javascript) - non_deferred
104
+ append_javascript_pack_tag(*names, defer: defer, async: async)
105
+ sync = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:sync], type: :javascript)
106
+ async = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:async], type: :javascript) - sync
107
+ deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:deferred], type: :javascript) - sync - async
107
108
 
108
109
  @javascript_pack_tag_loaded = true
109
110
 
110
111
  capture do
111
- concat javascript_include_tag(*deferred, **options.tap { |o| o[:defer] = true })
112
- concat "\n" if non_deferred.any? && deferred.any?
113
- concat javascript_include_tag(*non_deferred, **options.tap { |o| o[:defer] = false })
112
+ concat javascript_include_tag(*async, **options.dup.tap { |o| o[:async] = true })
113
+ concat "\n" if async.any? && deferred.any?
114
+ concat javascript_include_tag(*deferred, **options.dup.tap { |o| o[:defer] = true })
115
+ concat "\n" if sync.any? && deferred.any?
116
+ concat javascript_include_tag(*sync, **options)
114
117
  end
115
118
  end
116
119
 
@@ -179,27 +182,35 @@ module Shakapacker::Helper
179
182
  nil
180
183
  end
181
184
 
182
- def append_javascript_pack_tag(*names, defer: true)
183
- update_javascript_pack_tag_queue(defer: defer) do |hash_key|
185
+ def append_javascript_pack_tag(*names, defer: true, async: false)
186
+ update_javascript_pack_tag_queue(defer: defer, async: async) do |hash_key|
184
187
  javascript_pack_tag_queue[hash_key] |= names
185
188
  end
186
189
  end
187
190
 
188
- def prepend_javascript_pack_tag(*names, defer: true)
189
- update_javascript_pack_tag_queue(defer: defer) do |hash_key|
191
+ def prepend_javascript_pack_tag(*names, defer: true, async: false)
192
+ update_javascript_pack_tag_queue(defer: defer, async: async) do |hash_key|
190
193
  javascript_pack_tag_queue[hash_key].unshift(*names)
191
194
  end
192
195
  end
193
196
 
194
197
  private
195
198
 
196
- def update_javascript_pack_tag_queue(defer:)
199
+ def update_javascript_pack_tag_queue(defer:, async:)
197
200
  if @javascript_pack_tag_loaded
198
201
  raise "You can only call #{caller_locations(1..1).first.base_label} before javascript_pack_tag helper. " \
199
202
  "Please refer to https://github.com/shakacode/shakapacker/blob/main/README.md#view-helper-append_javascript_pack_tag-prepend_javascript_pack_tag-and-append_stylesheet_pack_tag for the usage guide"
200
203
  end
201
204
 
202
- yield(defer ? :deferred : :non_deferred)
205
+ # When both async and defer are specified, async takes precedence per HTML5 spec
206
+ hash_key = if async
207
+ :async
208
+ elsif defer
209
+ :deferred
210
+ else
211
+ :sync
212
+ end
213
+ yield(hash_key)
203
214
 
204
215
  # prevent rendering Array#to_s representation when used with <%= … %> syntax
205
216
  nil
@@ -207,8 +218,9 @@ module Shakapacker::Helper
207
218
 
208
219
  def javascript_pack_tag_queue
209
220
  @javascript_pack_tag_queue ||= {
221
+ async: [],
210
222
  deferred: [],
211
- non_deferred: []
223
+ sync: []
212
224
  }
213
225
  end
214
226
 
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "8.1.0".freeze
3
+ VERSION = "8.3.0".freeze
4
4
  end
@@ -1,5 +1,18 @@
1
- const { moduleExists } = require("shakapacker")
1
+ const { moduleExists, packageFullVersion } = require("../utils/helpers")
2
2
 
3
+ const coreJsVersion = () => {
4
+ try {
5
+ return packageFullVersion("core-js").match(/^\d+\.\d+/)[0]
6
+ } catch (e) {
7
+ if (e.code !== "MODULE_NOT_FOUND") {
8
+ throw e
9
+ }
10
+
11
+ return "3.8"
12
+ }
13
+ }
14
+
15
+ /** @param api {import("@babel/core").ConfigAPI} */
3
16
  module.exports = function config(api) {
4
17
  const validEnv = ["development", "test", "production"]
5
18
  const currentEnv = api.env()
@@ -9,9 +22,7 @@ module.exports = function config(api) {
9
22
 
10
23
  if (!validEnv.includes(currentEnv)) {
11
24
  throw new Error(
12
- `Please specify a valid NODE_ENV or BABEL_ENV environment variable. Valid values are "development", "test", and "production". Instead, received: "${JSON.stringify(
13
- currentEnv
14
- )}".`
25
+ `Please specify a valid NODE_ENV or BABEL_ENV environment variable. Valid values are "development", "test", and "production". Instead, received: "${currentEnv}".`
15
26
  )
16
27
  }
17
28
 
@@ -22,7 +33,7 @@ module.exports = function config(api) {
22
33
  "@babel/preset-env",
23
34
  {
24
35
  useBuiltIns: "entry",
25
- corejs: "3.8",
36
+ corejs: coreJsVersion(),
26
37
  modules: "auto",
27
38
  bugfixes: true,
28
39
  exclude: ["transform-typeof-symbol"]
@@ -4,6 +4,7 @@
4
4
  const { existsSync, readdirSync } = require("fs")
5
5
  const { basename, dirname, join, relative, resolve } = require("path")
6
6
  const extname = require("path-complete-extname")
7
+ // TODO: Change to `const { WebpackAssetsManifest }` when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
7
8
  const WebpackAssetsManifest = require("webpack-assets-manifest")
8
9
  const webpack = require("webpack")
9
10
  const rules = require("../rules")
@@ -72,10 +73,15 @@ const getModulePaths = () => {
72
73
  return result
73
74
  }
74
75
 
76
+ // TODO: Remove WebpackAssetsManifestConstructor workaround when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
77
+ const WebpackAssetsManifestConstructor =
78
+ "WebpackAssetsManifest" in WebpackAssetsManifest
79
+ ? WebpackAssetsManifest.WebpackAssetsManifest
80
+ : WebpackAssetsManifest
75
81
  const getPlugins = () => {
76
82
  const plugins = [
77
83
  new webpack.EnvironmentPlugin(process.env),
78
- new WebpackAssetsManifest({
84
+ new WebpackAssetsManifestConstructor({
79
85
  entrypoints: true,
80
86
  writeToDisk: true,
81
87
  output: config.manifestPath,
data/package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  declare module 'shakapacker' {
2
- import { Configuration } from 'webpack'
2
+ import { Configuration, RuleSetRule } from 'webpack'
3
+ import * as https from 'node:https';
3
4
 
4
5
  export interface Config {
5
6
  source_path: string
@@ -32,12 +33,48 @@ declare module 'shakapacker' {
32
33
  runningWebpackDevServer: boolean
33
34
  }
34
35
 
36
+ type Header = Array<{ key: string; value: string }> | Record<string, string | string[]>
37
+ type ServerType = 'http' | 'https' | 'spdy'
38
+ type WebSocketType = 'sockjs' | 'ws'
39
+
40
+ /**
41
+ * This has the same keys and behavior as https://webpack.js.org/configuration/dev-server/ except:
42
+ * 1. `hot` is replaced by `hmr`;
43
+ * 2. Camel-cased properties are replaced by snake-cased ones.
44
+ * @see {import('webpack-dev-server').Configuration}
45
+ */
46
+ interface DevServerConfig {
47
+ allowed_hosts?: 'all' | 'auto' | string | string[]
48
+ bonjour?: boolean | Record<string, unknown> // bonjour.BonjourOptions
49
+ client?: Record<string, unknown> // Client
50
+ compress?: boolean
51
+ dev_middleware?: Record<string, unknown> // webpackDevMiddleware.Options
52
+ headers?: Header | (() => Header)
53
+ history_api_fallback?: boolean | Record<string, unknown> // HistoryApiFallbackOptions
54
+ hmr?: 'only' | boolean
55
+ host?: 'local-ip' | 'local-ipv4' | 'local-ipv6' | string
56
+ http2?: boolean
57
+ https?: boolean | https.ServerOptions
58
+ ipc?: boolean | string
59
+ magic_html?: boolean
60
+ live_reload?: boolean
61
+ open?: boolean | string | string[] | Record<string, unknown> | Record<string, unknown>[]
62
+ port?: 'auto' | string | number
63
+ proxy?: unknown // ProxyConfigMap | ProxyConfigArray
64
+ setup_exit_signals?: boolean
65
+ static?: boolean | string | unknown // Static | Array<string | Static>
66
+ watch_files?: string | string[] | unknown // WatchFiles | Array<WatchFiles | string>
67
+ web_socket_server?: string | boolean | WebSocketType | { type?: string | boolean | WebSocketType, options?: Record<string, unknown> }
68
+ server?: string | boolean | ServerType | { type?: string | boolean | ServerType, options?: https.ServerOptions }
69
+ [otherWebpackDevServerConfigKey: string]: unknown
70
+ }
71
+
35
72
  export const config: Config
36
- export const devServer: Record<string, unknown>
73
+ export const devServer: DevServerConfig
37
74
  export function generateWebpackConfig(extraConfig?: Configuration): Configuration
38
75
  export const baseConfig: Configuration
39
76
  export const env: Env
40
- export const rules: Record<string, unknown>
77
+ export const rules: RuleSetRule[]
41
78
  export function moduleExists(packageName: string): boolean
42
79
  export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null
43
80
  export const inliningCss: boolean
@@ -1,20 +1,16 @@
1
1
  /* eslint global-require: 0 */
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
 
4
- const rules = {
5
- raw: require("./raw"),
6
- file: require("./file"),
7
- css: require("./css"),
8
- sass: require("./sass"),
9
- babel: require("./babel"),
10
- swc: require("./swc"),
11
- esbuild: require("./esbuild"),
12
- erb: require("./erb"),
13
- coffee: require("./coffee"),
14
- less: require("./less"),
15
- stylus: require("./stylus")
16
- }
17
-
18
- module.exports = Object.keys(rules)
19
- .filter((key) => !!rules[key])
20
- .map((key) => rules[key])
4
+ module.exports = [
5
+ require("./raw"),
6
+ require("./file"),
7
+ require("./css"),
8
+ require("./sass"),
9
+ require("./babel"),
10
+ require("./swc"),
11
+ require("./esbuild"),
12
+ require("./erb"),
13
+ require("./coffee"),
14
+ require("./less"),
15
+ require("./stylus")
16
+ ].filter(Boolean)
@@ -41,18 +41,22 @@ const loaderMatches = (configLoader, loaderToCheck, fn) => {
41
41
  return fn()
42
42
  }
43
43
 
44
- const packageMajorVersion = (packageName) => {
44
+ const packageFullVersion = (packageName) => {
45
45
  // eslint-disable-next-line import/no-dynamic-require
46
46
  const packageJsonPath = require.resolve(`${packageName}/package.json`)
47
47
  // eslint-disable-next-line import/no-dynamic-require, global-require
48
- return require(packageJsonPath).version.match(/^\d+/)[0]
48
+ return require(packageJsonPath).version
49
49
  }
50
50
 
51
+ const packageMajorVersion = (packageName) =>
52
+ packageFullVersion(packageName).match(/^\d+/)[0]
53
+
51
54
  module.exports = {
52
55
  isBoolean,
53
56
  ensureTrailingSlash,
54
57
  canProcess,
55
58
  moduleExists,
56
59
  loaderMatches,
60
+ packageFullVersion,
57
61
  packageMajorVersion
58
62
  }
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "8.1.0",
3
+ "version": "8.3.0",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -33,18 +33,18 @@
33
33
  "eslint": "^8.0.0",
34
34
  "eslint-config-airbnb": "^19.0.0",
35
35
  "eslint-config-prettier": "^9.0.0",
36
- "eslint-plugin-import": "^2.24.2",
36
+ "eslint-plugin-import": "^2.31.0",
37
37
  "eslint-plugin-jest": "^27.9.0",
38
- "eslint-plugin-jsx-a11y": "^6.4.1",
39
- "eslint-plugin-prettier": "^5.1.3",
40
- "eslint-plugin-react": "^7.26.0",
38
+ "eslint-plugin-jsx-a11y": "^6.10.2",
39
+ "eslint-plugin-prettier": "^5.2.6",
40
+ "eslint-plugin-react": "^7.37.5",
41
41
  "eslint-plugin-react-hooks": "^4.6.0",
42
- "jest": "^28.1.3",
42
+ "jest": "^29.7.0",
43
43
  "memory-fs": "^0.5.0",
44
44
  "prettier": "^3.2.5",
45
45
  "swc-loader": "^0.1.15",
46
46
  "thenify": "^3.3.1",
47
- "webpack": "^5.72.0",
47
+ "webpack": "5.93.0",
48
48
  "webpack-assets-manifest": "^5.0.6",
49
49
  "webpack-merge": "^5.8.0"
50
50
  },
@@ -55,11 +55,11 @@
55
55
  "@babel/runtime": "^7.17.9",
56
56
  "@types/babel__core": "^7.0.0",
57
57
  "@types/webpack": "^5.0.0",
58
- "babel-loader": "^8.2.4 || ^9.0.0",
58
+ "babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0",
59
59
  "compression-webpack-plugin": "^9.0.0 || ^10.0.0|| ^11.0.0",
60
60
  "terser-webpack-plugin": "^5.3.1",
61
- "webpack": "^5.72.0",
62
- "webpack-assets-manifest": "^5.0.6",
61
+ "webpack": "^5.76.0",
62
+ "webpack-assets-manifest": "^5.0.6 || ^6.0.0",
63
63
  "webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0",
64
64
  "webpack-dev-server": "^4.9.0 || ^5.0.0",
65
65
  "webpack-merge": "^5.8.0 || ^6.0.0"
data/test/helpers.js CHANGED
@@ -9,7 +9,7 @@ const createTrackLoader = () => {
9
9
  filesTracked,
10
10
  (source) => {
11
11
  filesTracked[source.resource] = true
12
- return source
12
+ return "" // Fix #567
13
13
  }
14
14
  ]
15
15
  }