-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support chunk lazy loading #15
Comments
There's not a straight path I can see for relying on Webpack's internal chunking with this approach. You'll have to split out the lazy-loaded elements into their own entry points. That's a bit of a trade-off with this model. |
There's an alternative: Disable Sprockets fingerprinting and rely on the bundlers' content hash. Webpack and Esbuild have them, but I'm not sure about Rollup. I don't think we currently have way to tell Sprockets to skip the digest of specific file types, but I think we could add a def stat_digest(path, stat)
if stat.directory?
# If its a directive, digest the list of filenames
digest_class.digest(self.entries(path).join(','.freeze))
elsif stat.file?
# If its a file, digest the contents
digest_class.file(path.to_s).digest unless Sprockets.skip_digest_for.include?(File.extname(path))
else
raise TypeError, "stat was not a directory or file: #{stat.ftype}"
end
end With this the files will still have a different name if they changed, keeping the expected functionality, Sprockets will still add them to its manifest file, but any bundler feature that relies on the file name (like lazy loading chunks) will not break. |
Please do investigate that! I like the idea that Sprockets could even auto-detect a pattern, and just skip files that have already been digested. |
I considered auto detection, but thought it might be considered too brittle. |
I'd say we'd want to just auto-detect that the digest format matches what sprockets is already generating. And then we'll recommend/configure webpack to produce in the same format (same length/charset). |
Here it goes: rails/sprockets#714. |
What's needed for the default webpack config to produce chunks with hashes in them? Also, I vaguely remember that if you digest hashes, there's an issue with the webpack dev server? |
This should config should match what sprockets currently does: output: {
path: path.resolve(__dirname, 'app/assets/builds'),
filename: '[name]-[contenthash].js',
chunkFilename: '[name]-[contenthash].chunk.js',
hashFunction: 'sha256',
hashDigestLength: 64
}, The current version of sprockets, public/assets/fudgeballs-3e05d90ab38c43e41a3f1400bbae046de14b44df67edf0506fa0d68d744eaf20-9c3018226817bce637b2a39a98e52a3c818cd7e0ed397ed530353373919744e5.js
public/assets/fudgeballs_active_storage-8ff22166fa4c88fb20d8dfe95327a62decbd85690571a8ed9bffa261eb30ac8c.chunk-86b6f708a1df94cf6bf29f7177b184f6a86037d4bf396a0659d0dbc6b2a84931.js
public/assets/fudgeballs_clipboard-8ff22166fa4c88fb20d8dfe95327a62decbd85690571a8ed9bffa261eb30ac8c.chunk-701271f31e3fb24353d1e9e50a24d127f0391befcf5e13fe3074467e6304befa.js The PR version correctly skips the fingerprinting: public/assets/fudgeballs-3e05d90ab38c43e41a3f1400bbae046de14b44df67edf0506fa0d68d744eaf20.js
public/assets/fudgeballs_active_storage-ba5fc1e55a50a4647ee6e65f8270a22ddec5cf72618fbd5c71088cb0f2b9065d.chunk.js
public/assets/fudgeballs_clipboard-8ff22166fa4c88fb20d8dfe95327a62decbd85690571a8ed9bffa261eb30ac8c.chunk.js There is however one configuration that Webpacker 5/6 has that I'm still missing and will try to find. This is the n.u = e=>"js/" + {
712: "fudgeballs_active_storage",
875: "fudgeballs_clipboard",
998: "trix"
}[e] + "-" + {
712: "de39e19b81af6079c9306c79ae331ebc",
717: "9957ff62fb2b6e6cbc110ad795b009f4",
998: "4afa271d098ab9c491abfbf4838c7bf2"
}[e] + ".chunk.js", This is what i.u = t=>({
712: "fudgeballs_active_storage",
717: "fudgeballs_invitations",
875: "fudgeballs_clipboard"
}[t] + ".js") So I've figured out how to produce sprockets like digests, but I still need to figure out how to tell Webpack to properly compile the js code with the digest. |
I'm not sure what you mean by 'digest hashes', but Webpacker 5/6 are working out of the box, no extra configs needed and the dev-server fine. We've been in this setup for months and both our local and production environments have to trouble finding the chunks. The |
Ah. Sweet. So you’re all good with your fork if sprockets with the patch? |
Yup, it's all working now: #21 👍 |
We also use lazy loading through webpack, especially with stimulusjs controllers which rely on large js libraries. We tested jsbundling with esbuild. Loved the speed of esbuild over webpack, but couldn't get lazy loading modules to work because esbuild only supports "splitting" and lazy loading with ESM output. We can't use ESM modules because importmap-rails requires Rails 7 in order to use ActionCable. |
Will have some alpha releases of those Rails JS modules that include ESM shortly. Hoping next week 👍 |
Would love to see something similar implemented for esbuild. I'm struggling to get chunks to work correctly and running into all kinds of weird precompilation issues on Heroku as a result of my workaround. Unfortunately esbuild uses Base32 for digests: So it would either require an upstream patch to esbuild to support md5 or some other hash function (which I would find unlikely given the highly opinionated approach), or adding support for Base32 digests in sprockets. Biggest downside of the latter would be that Base32 is not supported by ruby's standard library and might require adding an optional dependency on the |
Here are the changes required in esbuild to use SHA256 for digests: Not sure what's the best approach for getting this merged upstream, but it makes Binaries published on npm for testing: |
With esbuild, can't we lean on the same .digested marker? Why do we need to know the digest format? |
@theodorton How are you handling the entrypoint? On my tests with esbuild I could not make it generate chunks with digest and the entrypoints without the digest. If the entrypoint has a digest, the asset helper cannot find in the manifest. |
(thank you for jsbundling and rails, really enjoying the speed compared to webpack!) Here is one solution that works for esbuild + preact + esm modules in the browser: Note the --splitting and --format=esm flags esbuild app/javascript/*.* --bundle --outdir=app/assets/builds --loader:.js=jsx --jsx-factory=h --jsx-fragment=Fragment --splitting --format=esm" Add to gemfile, this leaves the chunks untouched by creating a non-digest copy, and served by rails, for use by dynamic imports gem "non-stupid-digest-assets" |
I'm not sure if the usage of the gem "non-stupid-digest-assets" is still needed now @DTrejo ? |
I got splitting working for esbuild by using the following configuration in my esbuild.config.js file const watch = process.argv.includes('--watch') && {
onRebuild (error) {
if (error) console.error('[watch] build failed', error)
else console.log('[watch] build finished')
}
}
require('esbuild').build({
entryPoints: ['app/javascript/application.js', 'app/javascript/admin.js'],
bundle: true,
outdir: 'app/assets/builds',
sourcemap: true,
splitting: true,
chunkNames: '[name]-[hash].digested',
format: 'esm',
watch: watch
}).catch(() => process.exit(1)) I am still on sprockets and in order for this config to work we need this pull request from @dhh. I guess a similar pull request is needed in order to make it work with propshaft. Also if i can recall correctly, splitting requires esm output format. That means that we have to include our js files like this:
|
Hi, I'm trying to get this working with
but I would rather expect/want to see something like this
Has anyone made this work? |
@joker-777 I suppose you don't want This is how our webpack output config is: module.exports = (env) => ({
mode: "production",
target: "web",
entry: entry("./app/javascript/packs/*.js"),
output: {
// filename: "[name]-[contenthash].digested.js", // asset pipeline expects the names without hashes
chunkFilename: "[name]-[contenthash].digested.js",
sourceMapFilename: "[file]-[fullhash].digested.map",
path: path.resolve(__dirname, "app/assets/builds"),
pathinfo: true,
publicPath: "/assets/",
clean: {
keep: /.keep/,
},
},
// ...
}); |
@tsrivishnu Thanks a lot for your response. It makes completely sense :) |
@tsrivishnu How do you handle images (png) which you import into your javascript files e.g. |
Nevermind, |
in esbuild I had an issue with how the images are hashed, I've added a monkey patch for sprockets, if anyone interested put this in: /config/initializers/sprockets_patch.rb # remove this when https://github.com/rails/sprockets/pull/726
Sprockets::DigestUtils.module_eval do |variable|
def already_digested?(name)
name =~ /-([0-9a-zA-Z]{7,128})\.digested/
end
end for the esbuild config I've used: publicPath: "/assets",
assetNames: '[name]-[hash].digested',
outdir: 'app/assets/builds', |
This is now supported in webpack/esbuild and propshaft/sprockets. |
Got any links to examples of this? |
@AlecRust If you are still looking for an answer, I have one for Webpack that I condensed from this thread, where I digest chunks and let sprockets to digest entrypoints.
|
Our production app using Webpacker 6 currently has many files that employ lazy loading of chunks:
We are switching to
jsbundling
, but the code above is broken. It seems that Webpack can't find the chunk because of the fingerprint that Sprockets add to the end of the file name.Is this possible with
jsbundling
right now, or would it be necessary for me to open a PR to add the support? I tried reading through the Webpacker code, but couldn't figure out what it was doing to make this work.The text was updated successfully, but these errors were encountered: