-
Notifications
You must be signed in to change notification settings - Fork 920
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
Idea for zero-configuration #68
Comments
The overall workflow would go like this:
|
Thanks for writing this up, this was first proposed in #49 and I really like the idea. There are actually two "scan your imports" related tasks here that I'd like tackle together, if possible:
The one concern I have with both of these is, are we breaking the promise: "you only need to re-run @pika/web when your dependencies change?" Would that mean we should implement both as an opt-in flag to start? (actually, I think you could tie #2 to our I'm going to be on vacation for the next two weeks, and won't have regular access to internet. Are you interested in taking a stab at this? If so, feel free to hold off until I get back, or do a first pass and I can commit to reviewing it as soon as I'm back online. |
@FredKSchott Hope you have a relaxing vacation. If before you leave you have any time to fit in some brainstorming, I'm not going anywhere, so feel free to join me. To make sure I understand the two related import-scanning tasks:
Good point that the second one may need to run when your code changes, even if your dependencies don't change. You may change the import to say The way we would solve that using @pika/web right now would be to either (a) accept that you're getting the full lodash with no tree-shaking, or (b) be more specific within webDependencies, which results in a slimmer output but already requires an extra step. So if we want that kind of added output efficiency, I don't think we're going to get away from having an extra build step. That said, it would be a good use-case for a web watcher. Besides, a good argument for a web watcher is that we already need a dev server anyway, since the Thus @pika/web's new responsibility would be:
|
And taking that last comment a step further:
It would play a very similar role to e.g. create-react-app, where:
(Sorry, I may have went a little overboard with lists today. Oops.) I think this is a good path forward for Pika as a very strong alternative to create-react-app, personally. Especially if we can get the React ecosystem working well via #62. |
Additionally, the entire time that Pika's watcher is running, any time that your code changes, your web_modules will always be up-to-date and ready for deployment. So you could quit the watcher immediately and just deploy, no need for a separate build phase like create-react-app has. This use-case is made stronger by the fact that HTTP/2 is widely supported, so there's no issue about serving 20-50 dependencies as individual JavaScript files, and 50-100 of the user's own un-bundled JavaScript files. The user can add their own bundling step if they want, as you advocate, but they don't have to. Overall I feel even more like this direction is strong competition with create-react-app. |
The only thing about this plan that I slightly don't like, is that user code would look like this: import React from "/web_modules/react";
import styled, { keyframes } from "/web_modules/styled-components";
// ... It would feel much better to have this: import React from "react";
import styled, { keyframes } from "styled-components";
// ... But maybe that's just because it's how I'm used to doing things in Node.js and create-react-app. Either way, the first one would still be accurate, since dependencies would in fact show up inside web_modules after Pika is done compiling them for you. And thus IDE support would still be feasible, even if it may sometimes need configuring where "root" would be (e.g. However, there may be a use-case for a separate "build" mode to disable emitting source maps and disable minification and other optimizations. That's a bigger part of this plan I'm not a fan of. I like the simplicity of there being "no actual build step", but simply Pika saying "here let me compile all your dependencies for you live". In the future this could be handled by a smarter package manager, which installs ESM-compatible modules directly into web_modules for you, but for now, that's the role Pika plays. (Although we'd still need a dev server anyway, because of the |
The "bare import" idea, I believe there are a few repos out there that claim to solve the registration problem, following the dormant ECMA spec. And workflow tools like webpack can do it but at the cost of a lot of workflow, and compiling JS every update. Node has a nice resolution strategy, but is bound to package.json and common conventions. And rollup has rollup-plugin-node-resolve, but is limited to package.json metadata AFAIK. https://github.com/standard-things/esm is a superb solution for importing modules into node. Possibly a similar loader could be used by us? I.e. insert a smart loader at the top of each module that can resolve the rest. |
An alternative to the Pika/watcher plan would be to just be okay with over-bundling during development phase, putting every module (not even just whitelisted) into web_modules, and only cleaning, minifying, and tree-shaking them during a production build. Then |
@backspaces My comment you replied to was kind of tangential. I love the idea that Pika never touches or compiles your source files -- they exist as-is, and all Pika ever does is make your dependencies more convenient to use. But in order for user source code to remain as-is, its import paths need to be correct both before compilation of dependencies and after, i.e. for Pika to resolve dependencies and for the browser to resolve dependencies. But I'm mostly satisfied with the |
Since currently Pika scans either dependencies or webDependencies, it doesn't have code to scan a project's source code. So I prototyped out a little code that uses the "find-imports" library: findImports(['public/**/*.js'], {
flatten: true,
absoluteImports: true,
relativeImports: false,
packageImports: true
})
.filter(s => s.startsWith('/web_modules/'))
.map(s => s
.replace(/^\/web_modules\//, '')
.replace(/.jsx?$/, '')) This works well and correctly returns a value of However, it also gives a The ability to also find dynamic import() expressions is very useful for this feature so it's worth looking into further. |
It just occurred to me that we can't reliably determine a path that's passed to dynamic |
I could not get scanning of dynamic imports working using the find-imports package, but I did it to work using acorn, acorn-walk, and acorn-dynamic-import. But these will need file-finding (glob?) functionality to actually get the JS source to pass to acorn, which does not sound fun. |
Got the majority of this working in small independent snippets. Gonna try to tie it all together now in my fork. |
Just realized that my idea of scanning the project for .js files doesn't take into account embedded JS inside HTML (within a module script tag). Maybe there's a higher-level library that can scan all JS code, even embedded code, within a directory. This sounds like something webpack probably already knows how to do. |
I've gotten the vast majority of this feature working, and have tested it in the browser. All that's mainly left is to figure out how to implement cache busting (maybe versioning) to address #69. Will collaborate on that issue to see if we can find some reasonable solution. Besides that, I think both #68 and #62 are ready for final implementation followed by a PR! |
Using a file watcher (chokidar), my prototype is able to install dependencies as-needed, by scanning imports. It looks for the package name by removing $prefix from beginning, removing ".js" from the end, and then finding the first path item. For example, given "/web_modules/styled-icons/fa-solid/Clipboard", it will give you "styled-icons". But it doesn't take into account aliased NPM packages. For example, the sample code I've been testing my prototype on installs @react/esm in place of "react", like so: npm i react@npm:@reactesm/react So that all packages that I figured that the Rollup plugin I described in #62 might be a good place to map this over, so that when Rollup sees "react", it would replace it with "@reactesm/react" no matter which file (mine or a dependency) imported "react". That's the solution I'm in the middle of working on right now, in case anyone has any insights or thoughts about this. Currently it's not working because it says:
It's clearly looking for a file, which makes sense for the other solution I mentioned in #62: const aliasesOstensiblyFromPackageJson = {
'styled-components': 'node_modules/styled-components/dist/styled-components.browser.cjs.js',
};
function renameModuleAliases(aliases) {
return {
name: 'pika:rename-module-aliases',
resolveId(src, loader) {
return aliases[src] || null;
},
};
}
// ...
plugins: [
replaceProcessEnv(),
renameModuleAliases(aliasesOstensiblyFromPackageJson),
resolve(),
commonjs(),
],
// ... So I guess what I need to do is make another Rollup plugin, one that maps over one dependency name to another dependency name, not file path. |
My somewhat-hacky solution to this is to install the dependencies like this: const { execSync } = require('child_process');
const npmAliases = {
'react': 'npm:@reactesm/react',
'react-dom': 'npm:@reactesm/react-dom',
};
const depStrings = deps
.map(([name, version]) => makeName(name, npmAliases))
.unique()
.join(' ');
execSync(`npm install ${depStrings}`); Which results in running this: $ npm install chart.js \
htm \
react-dom@npm:@reactesm/react-dom \
react@npm:@reactesm/react \
styled-components \
styled-icons This way it's more obvious how it works. |
After doing a little work to make sure this code only runs when a dependency isn't found on It's currently at https://github.com/sdegutis/dep-bundler-prototype. I'm going to try to turn it into a NPM CLI utility and publish it to NPM. It's full of edge-cases because I only focused on the evergreen-path, since it's a proof-of-concept, but I think it's a good start. Also, it's not a fork of this repo because it shares almost no code in common with it. Somehow, I was able to get a fully working prototype using only code found in these three issues and my own additions & glue code. But looking at the source to pika/web, it shares almost nothing in common, so I'm not exactly sure what pika/web does that my prototype doesn't do. |
Just pinging everyone to say that I haven't dropped this! I've also done some thinking on it since the original discussion and I think I have a suggestion for how to implement this without breaking the promise of "only run when your dependencies change". Because tree-shaking is a production optimization (the perf impact when serving local files in development should be minimal) lets add this "Import Analysis" feature behind the existing production We'd probably need a new ## Example: You write & serve JS from a "src/" directory to production
pika install --optimize --optimize-from src/**
## Example: You write TypeScript in a src/ directory, and then build that to a "static/js" directory
pika install --optimize --optimize-from static/js/** |
🚚 This feature request has been moved! 🆕 Pika Discuss: A Q&A discussion board for your JavaScript packages |
What if @pika/web could scan your project for root-level ESM imports, and automatically make them available?
The main benefit of this plan would be that you could skip white-listing modules and sub-modules in larger projects, and still get an optimally small web_modules directory.
For example, to more efficiently use styled-icons, I'm currently whitelisting individual icons like "styled-icons/fa-solid/Clipboard" and then importing those directly.
One way it could work is, if you have
import preact from 'preact'
inside "public/main.js", then it would see that you depend on the 'preact' module and it would export that. Similarly, if you haveimport {html} from 'htm/preact'
then it would know it needs to export "htm/preact.js".However, that has the flaw of the code not really being usable from the browser until the module paths are changed, since they would be using prefix-less names that (I'm assuming) assume the current directory, which wouldn't be the case.
An alternative solution is that user code has to always prefix paths with "/web_modules/" so that it will be correct after @pika/web exports the modules to that directory. This would also let @pika/web scan them, filter only ones that have this prefix, and remove the prefix to determine the actual package name.
@FredKSchott what do you think?
The text was updated successfully, but these errors were encountered: