webpacker 3.0.1 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintignore +3 -0
- data/.gitignore +0 -2
- data/.travis.yml +3 -3
- data/CHANGELOG.md +45 -1
- data/Gemfile.lock +144 -0
- data/README.md +23 -7
- data/docs/deployment.md +3 -6
- data/docs/env.md +1 -1
- data/docs/misc.md +1 -1
- data/docs/props.md +119 -1
- data/docs/testing.md +103 -0
- data/docs/typescript.md +2 -1
- data/docs/webpack-dev-server.md +44 -1
- data/docs/webpack.md +37 -1
- data/exe/webpack +8 -0
- data/exe/webpack-dev-server +8 -0
- data/lib/install/config/webpacker.yml +9 -2
- data/lib/install/elm.rb +11 -9
- data/lib/install/examples/elm/hello_elm.js +4 -3
- data/lib/install/examples/vue/hello_vue.js +1 -1
- data/lib/install/template.rb +3 -5
- data/lib/install/vue.rb +7 -5
- data/lib/tasks/webpacker/check_node.rake +6 -3
- data/lib/webpacker/configuration.rb +4 -0
- data/lib/webpacker/dev_server.rb +19 -5
- data/lib/webpacker/dev_server_proxy.rb +1 -1
- data/lib/webpacker/dev_server_runner.rb +51 -0
- data/lib/webpacker/helper.rb +5 -16
- data/lib/webpacker/manifest.rb +8 -8
- data/lib/webpacker/runner.rb +22 -0
- data/lib/webpacker/version.rb +1 -1
- data/lib/webpacker/webpack_runner.rb +15 -0
- data/package.json +4 -2
- data/package/config.js +19 -2
- data/package/environment.js +3 -3
- data/package/environments/development.js +15 -11
- data/package/environments/production.js +2 -2
- data/package/index.js +1 -1
- data/package/loaders/file.js +4 -4
- data/package/loaders/style.js +2 -2
- data/package/loaders/vue.js +2 -2
- data/test/configuration_test.rb +5 -0
- data/test/manifest_test.rb +10 -2
- data/webpacker.gemspec +3 -1
- data/yarn.lock +5133 -0
- metadata +16 -8
- data/lib/install/bin/webpack-dev-server.tt +0 -68
- data/lib/install/bin/webpack.tt +0 -27
@@ -2,7 +2,7 @@ require "rack/proxy"
|
|
2
2
|
|
3
3
|
class Webpacker::DevServerProxy < Rack::Proxy
|
4
4
|
def rewrite_response(response)
|
5
|
-
|
5
|
+
_status, headers, _body = response
|
6
6
|
headers.delete "transfer-encoding"
|
7
7
|
headers.delete "content-length" if Webpacker.dev_server.running? && Webpacker.dev_server.https?
|
8
8
|
response
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
require "yaml"
|
3
|
+
require "socket"
|
4
|
+
require "webpacker/runner"
|
5
|
+
|
6
|
+
module Webpacker
|
7
|
+
class DevServerRunner < Webpacker::Runner
|
8
|
+
def run
|
9
|
+
load_config
|
10
|
+
detect_port!
|
11
|
+
execute_cmd
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def load_config
|
16
|
+
@config_file = File.join(@app_path, "config/webpacker.yml")
|
17
|
+
dev_server = YAML.load_file(@config_file)[ENV["RAILS_ENV"]]["dev_server"]
|
18
|
+
|
19
|
+
@hostname = dev_server["host"]
|
20
|
+
@port = dev_server["port"]
|
21
|
+
|
22
|
+
rescue Errno::ENOENT, NoMethodError
|
23
|
+
$stdout.puts "Webpack dev_server configuration not found in #{@config_file}."
|
24
|
+
$stdout.puts "Please run bundle exec rails webpacker:install to install webpacker"
|
25
|
+
exit!
|
26
|
+
end
|
27
|
+
|
28
|
+
def detect_port!
|
29
|
+
server = TCPServer.new(@hostname, @port)
|
30
|
+
server.close
|
31
|
+
|
32
|
+
rescue Errno::EADDRINUSE
|
33
|
+
$stdout.puts "Another program is running on port #{@port}. Set a new port in #{@config_file} for dev_server"
|
34
|
+
exit!
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute_cmd
|
38
|
+
env = { "NODE_PATH" => @node_modules_path.shellescape }
|
39
|
+
cmd = [
|
40
|
+
"#{@node_modules_path}/.bin/webpack-dev-server",
|
41
|
+
"--progress",
|
42
|
+
"--color",
|
43
|
+
"--config", @webpack_config
|
44
|
+
]
|
45
|
+
|
46
|
+
Dir.chdir(@app_path) do
|
47
|
+
exec env, *cmd
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/webpacker/helper.rb
CHANGED
@@ -2,26 +2,19 @@ module Webpacker::Helper
|
|
2
2
|
# Computes the full path for a given webpacker asset.
|
3
3
|
# Return relative path using manifest.json and passes it to asset_url helper
|
4
4
|
# This will use asset_path internally, so most of their behaviors will be the same.
|
5
|
-
# Examples:
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# In production mode:
|
6
|
+
# Example:
|
7
|
+
#
|
10
8
|
# <%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css"
|
11
9
|
def asset_pack_path(name, **options)
|
12
|
-
asset_path(Webpacker.manifest.lookup(name), **options)
|
10
|
+
asset_path(Webpacker.manifest.lookup!(name), **options)
|
13
11
|
end
|
14
12
|
# Creates a script tag that references the named pack file, as compiled by Webpack per the entries list
|
15
13
|
# in config/webpack/shared.js. By default, this list is auto-generated to match everything in
|
16
14
|
# app/javascript/packs/*.js. In production mode, the digested reference is automatically looked up.
|
17
15
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# # In development mode:
|
21
|
-
# <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
|
22
|
-
# <script src="/packs/calendar.js" data-turbolinks-track="reload"></script>
|
16
|
+
# Example:
|
23
17
|
#
|
24
|
-
# # In production mode:
|
25
18
|
# <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
|
26
19
|
# <script src="/packs/calendar-1016838bab065ae1e314.js" data-turbolinks-track="reload"></script>
|
27
20
|
def javascript_pack_tag(*names, **options)
|
@@ -37,10 +30,6 @@ module Webpacker::Helper
|
|
37
30
|
#
|
38
31
|
# Examples:
|
39
32
|
#
|
40
|
-
# # In development mode:
|
41
|
-
# <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
|
42
|
-
# <link rel="stylesheet" media="screen" href="/packs/calendar.css" data-turbolinks-track="reload" />
|
43
|
-
#
|
44
33
|
# # In development mode with hot module replacement:
|
45
34
|
# <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
|
46
35
|
# nil
|
@@ -56,7 +45,7 @@ module Webpacker::Helper
|
|
56
45
|
|
57
46
|
private
|
58
47
|
def sources_from_pack_manifest(names, type:)
|
59
|
-
names.map { |name| Webpacker.manifest.lookup(pack_name_with_extension(name, type: type)) }
|
48
|
+
names.map { |name| Webpacker.manifest.lookup!(pack_name_with_extension(name, type: type)) }
|
60
49
|
end
|
61
50
|
|
62
51
|
def pack_name_with_extension(name, type:)
|
data/lib/webpacker/manifest.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
# Singleton registry for accessing the packs path using a generated manifest.
|
2
2
|
# This allows javascript_pack_tag, stylesheet_pack_tag, asset_pack_path to take a reference to,
|
3
|
-
# say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar.js" or
|
4
|
-
# "/packs/calendar.css"
|
5
|
-
#
|
6
|
-
# In production mode, it returns compiles files, like
|
7
|
-
# "/packs/calendar-1016838bab065ae1e314.js" and "/packs/calendar-1016838bab065ae1e314.css",
|
8
|
-
# for long-term caching.
|
3
|
+
# say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar-1016838bab065ae1e314.js" or
|
4
|
+
# "/packs/calendar-1016838bab065ae1e314.css".
|
9
5
|
#
|
10
6
|
# When the configuration is set to on-demand compilation, with the `compile: true` option in
|
11
7
|
# the webpacker.yml file, any lookups will be preceeded by a compilation if one is needed.
|
@@ -27,6 +23,10 @@ class Webpacker::Manifest
|
|
27
23
|
find name
|
28
24
|
end
|
29
25
|
|
26
|
+
def lookup!(name)
|
27
|
+
lookup(name) || handle_missing_entry(name)
|
28
|
+
end
|
29
|
+
|
30
30
|
private
|
31
31
|
def compiling?
|
32
32
|
config.compile? && !dev_server.running?
|
@@ -37,7 +37,7 @@ class Webpacker::Manifest
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def find(name)
|
40
|
-
data[name.to_s]
|
40
|
+
data[name.to_s].presence
|
41
41
|
end
|
42
42
|
|
43
43
|
def handle_missing_entry(name)
|
@@ -45,7 +45,7 @@ class Webpacker::Manifest
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def missing_file_from_manifest_error(bundle_name)
|
48
|
-
|
48
|
+
<<-MSG
|
49
49
|
Webpacker can't find #{bundle_name} in #{config.public_manifest_path}. Possible causes:
|
50
50
|
1. You want to set webpacker.yml value of compile to true for your environment
|
51
51
|
unless you are using the `webpack -w` or the webpack-dev-server.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Webpacker
|
2
|
+
class Runner
|
3
|
+
def self.run(argv)
|
4
|
+
$stdout.sync = true
|
5
|
+
|
6
|
+
new(argv).run
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(argv)
|
10
|
+
@argv = argv
|
11
|
+
|
12
|
+
@app_path = File.expand_path(".", Dir.pwd)
|
13
|
+
@node_modules_path = File.join(@app_path, "node_modules")
|
14
|
+
@webpack_config = File.join(@app_path, "config/webpack/#{ENV["NODE_ENV"]}.js")
|
15
|
+
|
16
|
+
unless File.exist?(@webpack_config)
|
17
|
+
puts "Webpack config #{@webpack_config} not found, please run 'bundle exec rails webpacker:install' to install webpacker with default configs or add the missing config file for your custom environment."
|
18
|
+
exit!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/webpacker/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
require "webpacker/runner"
|
3
|
+
|
4
|
+
module Webpacker
|
5
|
+
class WebpackRunner < Webpacker::Runner
|
6
|
+
def run
|
7
|
+
env = { "NODE_PATH" => @node_modules_path.shellescape }
|
8
|
+
cmd = [ "#{@node_modules_path}/.bin/webpack", "--config", @webpack_config ] + @argv
|
9
|
+
|
10
|
+
Dir.chdir(@app_path) do
|
11
|
+
exec env, *cmd
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@rails/webpacker",
|
3
|
-
"version": "3.0.
|
3
|
+
"version": "3.0.2",
|
4
4
|
"description": "Use Webpack to manage app-like JavaScript modules in Rails",
|
5
5
|
"main": "package/index.js",
|
6
6
|
"files": [
|
@@ -19,7 +19,6 @@
|
|
19
19
|
"babel-polyfill": "^6.26.0",
|
20
20
|
"babel-preset-env": "^1.6.0",
|
21
21
|
"coffee-loader": "^0.8.0",
|
22
|
-
"coffeescript": "^1.12.7",
|
23
22
|
"compression-webpack-plugin": "^1.0.0",
|
24
23
|
"css-loader": "^0.28.5",
|
25
24
|
"extract-text-webpack-plugin": "^3.0.0",
|
@@ -45,6 +44,9 @@
|
|
45
44
|
"eslint-plugin-jsx-a11y": "^4.0.0",
|
46
45
|
"eslint-plugin-react": "^6.10.0"
|
47
46
|
},
|
47
|
+
"peerDependencies": {
|
48
|
+
"coffeescript": ">= 1.12.7 || >= 2.x"
|
49
|
+
},
|
48
50
|
"scripts": {
|
49
51
|
"test": "echo \"Error: no test specified\" && exit 1",
|
50
52
|
"lint": "eslint {package,lib}/"
|
data/package/config.js
CHANGED
@@ -3,6 +3,23 @@ const { safeLoad } = require('js-yaml')
|
|
3
3
|
const { readFileSync } = require('fs')
|
4
4
|
|
5
5
|
const filePath = resolve('config', 'webpacker.yml')
|
6
|
-
const config = safeLoad(readFileSync(filePath), 'utf8')
|
6
|
+
const config = safeLoad(readFileSync(filePath), 'utf8')[process.env.NODE_ENV]
|
7
7
|
|
8
|
-
|
8
|
+
const isBoolean = str => /^true/.test(str) || /^false/.test(str)
|
9
|
+
|
10
|
+
const fetch = key =>
|
11
|
+
(isBoolean(process.env[key]) ? JSON.parse(process.env[key]) : process.env[key])
|
12
|
+
|
13
|
+
const devServer = (key) => {
|
14
|
+
const envValue = fetch(`WEBPACKER_DEV_SERVER_${key.toUpperCase().replace(/_/g, '')}`)
|
15
|
+
if (typeof envValue === 'undefined' || envValue === null) return config.dev_server[key]
|
16
|
+
return envValue
|
17
|
+
}
|
18
|
+
|
19
|
+
if (config.dev_server) {
|
20
|
+
Object.keys(config.dev_server).forEach((key) => {
|
21
|
+
config.dev_server[key] = devServer(key)
|
22
|
+
})
|
23
|
+
}
|
24
|
+
|
25
|
+
module.exports = config
|
data/package/environment.js
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
/* eslint global-require: 0 */
|
2
2
|
/* eslint import/no-dynamic-require: 0 */
|
3
3
|
|
4
|
-
const config = require('./config')
|
5
|
-
const assetHost = require('./asset_host')
|
6
|
-
|
7
4
|
const { basename, dirname, join, relative, resolve } = require('path')
|
8
5
|
const { sync } = require('glob')
|
9
6
|
const extname = require('path-complete-extname')
|
@@ -12,6 +9,9 @@ const webpack = require('webpack')
|
|
12
9
|
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
13
10
|
const ManifestPlugin = require('webpack-manifest-plugin')
|
14
11
|
|
12
|
+
const config = require('./config')
|
13
|
+
const assetHost = require('./asset_host')
|
14
|
+
|
15
15
|
function getLoaderMap() {
|
16
16
|
const result = new Map()
|
17
17
|
const paths = sync(resolve(__dirname, 'loaders', '*.js'))
|
@@ -1,13 +1,13 @@
|
|
1
|
+
const webpack = require('webpack')
|
1
2
|
const Environment = require('../environment')
|
2
|
-
const { dev_server } = require('../config')
|
3
|
+
const { dev_server: devServer } = require('../config')
|
3
4
|
const assetHost = require('../asset_host')
|
4
|
-
const webpack = require('webpack')
|
5
5
|
|
6
6
|
module.exports = class extends Environment {
|
7
7
|
constructor() {
|
8
8
|
super()
|
9
9
|
|
10
|
-
if (
|
10
|
+
if (devServer.hmr) {
|
11
11
|
this.plugins.set('HotModuleReplacement', new webpack.HotModuleReplacementPlugin())
|
12
12
|
this.plugins.set('NamedModules', new webpack.NamedModulesPlugin())
|
13
13
|
}
|
@@ -15,25 +15,29 @@ module.exports = class extends Environment {
|
|
15
15
|
|
16
16
|
toWebpackConfig() {
|
17
17
|
const result = super.toWebpackConfig()
|
18
|
-
if (
|
18
|
+
if (devServer.hmr) {
|
19
19
|
result.output.filename = '[name]-[hash].js'
|
20
20
|
}
|
21
21
|
result.output.pathinfo = true
|
22
22
|
result.devtool = 'cheap-eval-source-map'
|
23
23
|
result.devServer = {
|
24
|
-
host: dev_server.host,
|
25
|
-
port: dev_server.port,
|
26
|
-
https: dev_server.https,
|
27
|
-
hot: dev_server.hmr,
|
28
|
-
contentBase: assetHost.path,
|
29
|
-
publicPath: assetHost.publicPath,
|
30
24
|
clientLogLevel: 'none',
|
31
25
|
compress: true,
|
26
|
+
disableHostCheck: devServer.disable_host_check,
|
27
|
+
host: devServer.host,
|
28
|
+
port: devServer.port,
|
29
|
+
https: devServer.https,
|
30
|
+
hot: devServer.hmr,
|
31
|
+
contentBase: assetHost.path,
|
32
|
+
inline: devServer.inline,
|
33
|
+
useLocalIp: devServer.use_local_ip,
|
34
|
+
public: devServer.public,
|
35
|
+
publicPath: assetHost.publicPath,
|
32
36
|
historyApiFallback: true,
|
33
37
|
headers: {
|
34
38
|
'Access-Control-Allow-Origin': '*'
|
35
39
|
},
|
36
|
-
overlay:
|
40
|
+
overlay: devServer.overlay,
|
37
41
|
watchOptions: {
|
38
42
|
ignored: /node_modules/
|
39
43
|
},
|
@@ -1,6 +1,6 @@
|
|
1
|
-
const Environment = require('../environment')
|
2
1
|
const webpack = require('webpack')
|
3
2
|
const CompressionPlugin = require('compression-webpack-plugin')
|
3
|
+
const Environment = require('../environment')
|
4
4
|
|
5
5
|
module.exports = class extends Environment {
|
6
6
|
constructor() {
|
@@ -27,7 +27,7 @@ module.exports = class extends Environment {
|
|
27
27
|
|
28
28
|
toWebpackConfig() {
|
29
29
|
const result = super.toWebpackConfig()
|
30
|
-
result.devtool = 'source-map'
|
30
|
+
result.devtool = 'nosources-source-map'
|
31
31
|
result.stats = 'normal'
|
32
32
|
return result
|
33
33
|
}
|
data/package/index.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
/* eslint global-require: 0 */
|
2
2
|
/* eslint import/no-dynamic-require: 0 */
|
3
3
|
|
4
|
-
const Environment = require('./environment')
|
5
4
|
const { resolve } = require('path')
|
6
5
|
const { existsSync } = require('fs')
|
6
|
+
const Environment = require('./environment')
|
7
7
|
|
8
8
|
function createEnvironment() {
|
9
9
|
const path = resolve(__dirname, 'environments', `${process.env.NODE_ENV}.js`)
|
data/package/loaders/file.js
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
const config = require('../config')
|
2
|
-
const assetHost = require('../asset_host')
|
3
1
|
const { join } = require('path')
|
2
|
+
const { source_path } = require('../config')
|
3
|
+
const assetHost = require('../asset_host')
|
4
4
|
|
5
5
|
module.exports = {
|
6
|
-
test: /\.(jpg|jpeg|png|gif|svg|eot|ttf|woff|woff2)$/i,
|
6
|
+
test: /\.(jpg|jpeg|png|gif|svg|eot|otf|ttf|woff|woff2)$/i,
|
7
7
|
use: [{
|
8
8
|
loader: 'file-loader',
|
9
9
|
options: {
|
10
10
|
name: '[path][name]-[hash].[ext]',
|
11
|
-
context: join(
|
11
|
+
context: join(source_path),
|
12
12
|
publicPath: assetHost.publicPathWithHost
|
13
13
|
}
|
14
14
|
}]
|
data/package/loaders/style.js
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
2
2
|
const path = require('path')
|
3
|
-
const
|
3
|
+
const { dev_server: devServer } = require('../config')
|
4
4
|
|
5
5
|
const postcssConfigPath = path.resolve(process.cwd(), '.postcssrc.yml')
|
6
6
|
const isProduction = process.env.NODE_ENV === 'production'
|
7
|
-
const extractCSS = !(
|
7
|
+
const extractCSS = !(devServer && devServer.hmr)
|
8
8
|
|
9
9
|
const extractOptions = {
|
10
10
|
fallback: 'style-loader',
|
data/package/loaders/vue.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
const
|
1
|
+
const { dev_server: devServer } = require('../config')
|
2
2
|
|
3
3
|
const isProduction = process.env.NODE_ENV === 'production'
|
4
|
-
const extractCSS = !(
|
4
|
+
const extractCSS = !(devServer && devServer.hmr)
|
5
5
|
|
6
6
|
module.exports = {
|
7
7
|
test: /\.vue(\.erb)?$/,
|
data/test/configuration_test.rb
CHANGED
@@ -26,6 +26,11 @@ class ConfigurationTest < Webpacker::Test
|
|
26
26
|
assert_equal Webpacker.config.cache_path.to_s, cache_path
|
27
27
|
end
|
28
28
|
|
29
|
+
def test_extensions
|
30
|
+
webpacker_yml = YAML.load_file("lib/install/config/webpacker.yml")
|
31
|
+
assert_equal Webpacker.config.extensions, webpacker_yml["default"]["extensions"]
|
32
|
+
end
|
33
|
+
|
29
34
|
def test_cache_manifest?
|
30
35
|
with_node_env("development") do
|
31
36
|
refute reloaded_config.cache_manifest?
|
data/test/manifest_test.rb
CHANGED
@@ -1,19 +1,27 @@
|
|
1
1
|
require "webpacker_test_helper"
|
2
2
|
|
3
3
|
class ManifestTest < Minitest::Test
|
4
|
-
def test_lookup_exception
|
4
|
+
def test_lookup_exception!
|
5
5
|
manifest_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/public/packs", "manifest.json").to_s
|
6
6
|
asset_file = "calendar.js"
|
7
7
|
|
8
8
|
Webpacker.config.stub :compile?, false do
|
9
9
|
error = assert_raises Webpacker::Manifest::MissingEntryError do
|
10
|
-
Webpacker.manifest.lookup(asset_file)
|
10
|
+
Webpacker.manifest.lookup!(asset_file)
|
11
11
|
end
|
12
12
|
|
13
13
|
assert_match "Webpacker can't find #{asset_file} in #{manifest_path}", error.message
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_lookup_success!
|
18
|
+
assert_equal Webpacker.manifest.lookup!("bootstrap.js"), "/packs/bootstrap-300631c4f0e0f9c865bc.js"
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_lookup_nil
|
22
|
+
assert_nil Webpacker.manifest.lookup("foo.js")
|
23
|
+
end
|
24
|
+
|
17
25
|
def test_lookup_success
|
18
26
|
assert_equal Webpacker.manifest.lookup("bootstrap.js"), "/packs/bootstrap-300631c4f0e0f9c865bc.js"
|
19
27
|
end
|