I have a legacy Java app using JS inside JSPs. We statically import JS dependencies from filesystem into our JSP files, some of them are old JS dependencies, some of them are newer and use the import/export JS feature.
For the module-based dependencies I'd like to bundle them. Most if not all bundlers I am familiar with, like webpack/esbuild uses an entry point to analyze the usage of dependencies and then bundles all dependencies and all app code used from entry-point onwards and puts them all in one big file
My JSP page spit JS code like so
<script type="module">
// do some stuff...
import { RFB } from 'rfb.js' // dozen of js files
// referenced, that is why
// i want to bundle them
// into just one js file...
// do some stuff with RFB
</script>
Then in the a dummy entrypoint I'd like to do that same import in a separate esbuild project for the purpose of bundling the module in one js file.
The idea is to use a dummy entry point that imports the elements I know I use in my JSP application and use that project (webpack/esbuild) for the purpose of using its bundling functionality. However, the bundle usually has this format
1. (() => {
2. // - Dependencies 1, 2, N goes here
3. // - import/export statements removed.
4. //
5. // - entry-point code
6. // - app code
7. })();
Of course if I import this file into my JSP file I can't access the dependencies. I was thinking of doing one of these 2 things:
I could add in the entry-point something like: window.ClassIAmInterested = ClassIAmInterested then that class will be available in the global scope of my JSP page.
I could remove the lines 1 and 7 of the bundle and then everything would be available in the global scope.
And although either of the 2 options might work I don't fully understand the implications of doing it. I know I am trying to use the bundlers in a way they are not designed, since I explicitly want to use the entry point as a dummy code that will just import whatever I know I use in my JSP pages. So I need clarity regarding if what I am trying to do is ok or if there is a better way of doing it.
I have a React app created with CRA. The app is built for two environments: website and browser extension which replaces a speed dial.
Whereas it is nice to use dynamic imports for website, it would be good to bundle some pages into main chunk, so that there is no splashscreen when opening a new tab.
Do you have any idea how to conditionally use dynamic imports basing on environment variable?
I've tried the following approach:
import WebsktopEager from 'pages/app/Websktop';
const WebsktopLazy = lazy(() => import('pages/app/Websktop'));
const Websktop = process.env.REACT_APP_TARGET === 'extension' ? WebsktopEager : WebsktopLazy;
I expected that webpack's tree shaking would remove dead code basing on the condition, but WebsktopEager is still bundled into a main chunk, even though process.env.REACT_APP_TARGET !== 'extension'.
Based on your code, cherry-picked bundling won't happen just because process.env.REACT_APP_TARGET === 'extension' is present as you expected; because it'll only work on runtime!
So CRA will be optimistic while bundling the code, and will bundle eager imports as well.
What you can do is have two separate entry files, of sorts: For eg -
main.eager.js // containing your eager imports
main.lazy.js // containing your dynamic/lazy imports
Now have a custom webpack config to separately build the projects based on any condition, environment variables that you'd like to use. And the entry point for those bundles can be differentiated based on the above said files
But ofcourse custom webpack config would require you to execute npm run eject command. So take a look at articles about that online
My task is to introduce library to existing create react app based application.
This library need to be build to separate chunk and should not contain any contenthash in name. Ideally should be build to buildDir/js/widget.js and that is.
Currently all my ts are compiled to js during build and are served with contenthash in name.
I don't know how to build widget.js from src/widget/index.ts because entry point is src/index.ts and it never catch src/widget/index.ts because it is not imported anywhere in main entry point.
This widget.js later will be imported in thirdparty web apps via <script> tag and it will be used to initialize some library like MyLibrary.init(...) so I think webpack should also have some info so this one widget.js should export its methods in special way to the browser during importing external script.
What is best way to get this build proces to work. Also it could be really nice to have it also during development with hot updates.
I don't want contenthash in resulting buildDir/js/widget.js because I don't want to ask my customers every time I have new version to update their <script src="..."> for new file name.
Should I eject this CRA? I'm not sure even if I add another entry point that I will be able to control output file name for one entry point as it is and for another without contenthash.
Or maybe it will be better to create separate webpack config (next to unejected CRA) for this widget but then how to run everything in development mode with hot updates?
I'm using webpack 4.42.0 here in this project.
for your case maybe you need this:
https://dev.to/zhiyueyi/include-your-react-widgets-in-any-web-page-emj
https://github.com/ZhiyueYi/demo-react-web-widget
Maybe I'm trying to do something silly, but I've got a web application (Angular2+), and I'm trying to build it in an extensible/modular way. In particular, I've got various, well, modules for lack of a better term, that I'd like to be able to include or not, depending on what kind of deployment is desired. These modules include various functionality that is implemented via extending base classes.
To simplify things, imagine there is a GenericModuleDefinition class, and there are two modules - ModuleOne.js and ModuleTwo.js. The first defines a ModuleOneDefinitionClass and instantiate an exported instance ModuleOneDefinition, and then registers it with the ModuleRegistry. The second module does an analogous thing.
(To be clear - it registers the ModuleXXXDefinition object with the ModuleRegistry when the ModuleXXX.js file is run (e.g. because of some other .js file imports one of its exports). If it is not run, then clearly nothing gets registered - and this is the problem I'm having, as I describe below.)
The ModuleRegistry has some methods that will iterate over all the Modules and call their individual methods. In this example, there might be a method called ModuleRegistry.initAllModules(), which then calls the initModule() method on each of the registered Modules.
At startup, my application (say, in index.js) calls ModuleRegistry.initAllModules(). Obviously, because index.js imports the exported ModuleRegistry symbol, this will cause the ModuleRegistry.js code to get pulled in, but since none of the exports from either of the two Module .js files is explicitly referenced, these files will not have been pulled in, and so the ModuleOneDefinition and ModuleTwoDefinition objects will not have been instantiated and registered with the ModuleRegistry - so the call to initAllModules() will be for naught.
Obviously, I could just put meaningless references to each of these ModuleDefinition objects in my index.js, which would force them to be pulled in, so that they were registered by the time I call initAllModules(). But this requires changes to the index.js file depending on whether I want to deploy it with ModuleTwo or without. I was hoping to have the mere existence of the ModuleTwo.js be enough to cause the file to get pulled in and the resulting ModuleTwoDefinition to get registered with the ModuleRegistry.
Is there a standard way to handle this kind of situation? Am I stuck having to edit some global file (either index.js or some other file it references) so that it has information about all the included Modules so that it can then go and load them? Or is there a clever way to cause JavaScript to execute all the .js files in a directory so that merely copying the files it would be enough to get them to load at startup?
a clever way to cause xxJavaScriptxx Node.js to execute all the .js files in a directory:
var fs = require('fs') // node filesystem
var path = require('path') // node path
function hasJsExtension(item) {
return item != 'index.js' && path.extname(item) === '.js'
}
function pathHere(item) {
return path.join('.', item)
}
fs.readdir('./', function(err, list) {
if (err) return err
list.filter(hasJsExtension).map(pathHere).forEach(require) // require them all
})
Angular is pretty different, all the more if it is ng serve who checks if your app needs a module, and if so serves the corresponding js file, at any time needed, not at first load time.
In fact your situation reminds me of C++ with header files Declaration and cpp files with implementation, maybe you just need a defineAllModules function before initAllModules.
Another way could be considering finding out how to exclude those modules from ng-serve, and include them as scripts in your HTML before the others, they would so be defined (if present and so, served), and called by angular if necesary, the only cavehat is the error in the console if one script tag is not fetched, but your app will work anyway, if it supposed to do so.
But anyway, it would be declaring/defining those modules somewhere in ng-serve and also in the HTML.
In your own special case, and not willing to under-evalute ng-serve, but is the total js for your app too heavy to be served at once? (minified and all the ...), since the good-to-go solution may be one of the many tools to build and rebuild your production all.js from your dev js folder at will, or like you said, with a drag&drop in your folder.
Such tool is, again, server-side, but even if you only can push/FTP your javascript, you could use it in your prefered dev environment and just push your new version. To see a list of such tools google 'YourDevEnvironment bundle javascript'.
To do more with angular serve and append static js files under specific conditions, you should use webpack so the first option i see here is eject your webpack configuration and after that you can specify what angular should load or not.
With that said, i will give an example:
With angular cli and ng serve any external javascript files you wanna include, you have to put them inside the scripts array in the angular-cli.json file.However you can not control which file should be included and which one not.
By using webpack configuration you can specify all these thing by passing a flag from your terminal to the webpack config file and do all the process right there.
Example:
var env.commandLineParamater, plugins;
if(env.commandLineParamater == 'production'){
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js",
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\jquery\\dist\\jquery.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]}else{
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]
}
then:
module.exports = (env) => {
"plugins": plugins,
// other webpack configuration
}
The script.js bundle will be loaded before your main app bundle and so you can control what you load when you run npm run start instead of ng-serve.
To Eject your webpack configuration, use ng eject.
Generally speaking, when you need to control some of angular ng-serve working, you should extract your own webpack config and customize it as you want.
In my project I have long used require.js together with the pdf.js library. Pdf.js have until recently been putting itself on the global object. I could still use it in my requirejs config by using a shim. The pdfjs library will in turn load another library called pdf.worker. In order to find this module the solution was to add a property to the global PDFJS object called workerSrc and point to the file on disk. This could be done before or after loading the pdfjs library.
The pdfjs library uses the pdf.worker to start a WebWorker and to do so it needs the path to a source file.
When I tried to update the pdfjs library in my project to a new version (1.5.314) the way to load and include the library have changed to use UMD modules and now everything get's a bit tricky.
The pdfjs library checks if the environment is using requirejs and so it defines itself as a module named "pdfjs-dist/build/pdf". When this module loads it checks for a module named "pdfjs-dist/build/pdf.worker". Since I have another folder structure I have added them to my requirejs config object with a new path:
paths: {
"pdfjs-dist/build/pdf": "vendor/pdfjs/build/pdf",
"pdfjs-dist/build/pdf.worker": "vendor/pdfjs/build/pdf.worker"
}
This is to make the module loader to find the modules at all. In development this works great. When I try to use the requirejs optimizer in my grunt build step however, it will put all of my project files into one single file. This step will try to include the pdf.worker module as well and this generates an error:
Error: Cannot uglify2 file: vendor/pdfjs/build/pdf.worker.js. Skipping
it. Error is: RangeError: Maximum call stack size exceeded
Since the worker source needs to be in a single file on disk I don't want this module to be included.
So I've tried two different config-settings in the requirejs config.
The first attempt was to override the paths property in my grunt build options:
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
The second thing to test is to exclude it from my module:
modules: [{
name: "core/app",
exclude: [
"pdfjs-dist/build/pdf.worker"
]
}]
Both techniques should tell the optimizer not to include the module but both attempts ended up with the same error as before. The requirejs optimizer still tries to include the module into the build and the attempt to uglify it ends up with a RangeError.
One could argue that since the uglify step fails it will not be included and I can go about my bussiness, but if the uglify step should happen to start working at a new update of pdfjs - what then?
Can anyone help me figure out why the requirejs config won't just exclude it in the build step and how to make it do so.
I found out what the core of my problem was and now I have a way to solve the problem and make my build process to work. My build step in grunt is using grunt-contrib-requirejs and I needed to override some options in the config for this job.
I didn't want the pdf.worker module to be included in my concatenated and minified production code.
I didn't want r.js to minify it only to later exclude it from the concatenated file.
I tried to solve the first problem thinking that it would mean that the second problem also should be solved. When I figured out the two were separate I finally found a solution.
In the r.js example on github there is a property named fileExclusionRegExp. This is what I now use to tell r.js not to copy the file over to the build folder.
fileExclusionRegExp: /pdf.worker.js/
Second, I need to tell the optimizer to not include this module in the concatenated file. This is done by overriding the paths property for this module to the value of "empty:".
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
Now my grunt build step will work without errors and all is well.
Thanks to async5 for informing me about the bug with uglify and the pdf.worker. The workaround is applied in another grunt task that uglify the worker and copies it into the build-folder separately. The options object for the grunt-contrib-uglify task will need this property in order to not break the pdf.worker file:
compress: {
sequences: false
}
Now my project works great when built for production.