RequireJS - skipDirOptimize: true setting does not optimize my main module - javascript

If I use RequireJS to optimize my whole project my main module will not get optimized/uglified if I use the setting skipDirOptimize: true. From my understanding everything should be optimized except the non-build layer JS files. Is this a bug or me not understanding the correct usage of this parameter?
Here is my requirejs config:
{
appDir: '../project',
mainConfigFile: '../project/assets/js/main.js',
dir: '../httpdocs',
optimize: "uglify",
//Introduced in 2.1.2: If using "dir" for an output directory, normally the
//optimize setting is used to optimize the build layers (the "modules"
//section of the config) and any other JS file in the directory. However, if
//the non-build layer JS files will not be loaded after a build, you can
//skip the optimization of those files, to speed up builds. Set this value
//to true if you want to skip optimizing those other non-build layer JS
//files.
skipDirOptimize: true,
generateSourceMaps: false,
normalizeDirDefines: "skip",
uglify: {
toplevel: true,
ascii_only: true,
beautify: false,
max_line_length: 1000,
defines: {
DEBUG: ['name', 'false']
},
no_mangle: false
},
optimizeCss: "standard",
removeCombined: true,
modules: [
{
name: '../main'
}
]
}

The use of the relative path in your module is probably causing r.js to not recognise it as a build bundle at the point where it decides whether or not to optimize it.
I had a similar problem (build bundles not being optimized), not with a relative module path but with a paths config to allow my modules to be named differently to my folder structure:
({
...
skipDirOptimize: true,
paths: {
'MyLibrary': ''
},
modules: [
{ name: 'MyLibrary/Main' }
],
...
})
This causes the module name in r.js (2.1.8) to become /Main, so when it builds its _buildPathToModuleIndex mapping, the key will be incorrect due to having two slashes (e.g. C:\dev\project\output\\Main).
The way that the optimization loop decides if a module is a build bundle (and hence needs optimization even when skipDirOptimize: true) is by looking it up in the _buildPathToModuleIndex mapping using its filename (e.g. C:\dev\project\output\Main). Due to it being in the map with two slashes, it won't find it. Therefore it won't be considered to be a build bundle and won't be optimized.
Try putting some console.logs in r.js where it builds and accesses _buildPathToModuleIndex to see what it's putting in and what it uses to look it up.
For my problem, the solution was to add a paths entry for 'MyLibrary/Main': 'Main' (repetition unfortunately). I'm not sure what your project structure is, but how about if you set baseUrl: '../ and then simply call your module main ?

Related

Using non-fully-specified imports with nextjs and webpack 5

I am trying to use a library (a node module I don't control) that contains this import in the packaged code
import _omit from 'lodash/omit';
and it does not work with nextjs12 and webpack5. I get this error
info - Creating an optimized production build
info - Compiled successfully
info - Collecting page data .Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/dev/project/node_modules/lodash/omit' imported from /Users/dev/project/node_modules/library/es/library.js
Did you mean to import lodash/omit.js?
The library has "type": "module" in it, which I think is the cause of this?
I have seen that you need to add fullySpecifed: false, to the webpack loaders, and I have tried that a few different ways.
In my next config, i have
experimental: { esmExternals: true, fullySpecified: false },
I also tried doing this to the config in the webpack function.
config.module.rules.push({
test: /\.m?js$/,
type: 'javascript/auto',
resolve: {
fullySpecified: false,
},
});
Is there something i am missing to make this work? It seems like it might have to do with the fact that this is during "collecting page data" and not "build"?
Node version: 14.16.0
yarn 1

Tree-shaking with rollup

I have a project in which I bundle a components library using Rollup (generating a bundle.esm.js file). These components are then used in another project, that generates web pages which use these components - each page is using different components.
The problem is, that the entire components library is always bundled with the different page bundles, regardless of which components I'm using, unnecessarily increasing the bundle size.
This is my Rollup setup:
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';
const extensions = [
'.js', '.jsx', '.ts', '.tsx',
];
export default [
{
input: './src/base/index.ts',
plugins: [
peerDepsExternal(),
resolve({ extensions }),
babel({
exclude: 'node_modules/**',
extensions,
}),
commonjs(),
],
output: [
{ file: pkg.main, format: 'cjs', sourcemap: true },
{ file: pkg.module, format: 'es', sourcemap: true },
],
watch: {
clearScreen: false,
},
},
];
I have "modules" set to false in webpack, as well.
There are things you will need to do to achieve treeshakable code from both sides - the built package and the project using it.
From your code snippet, I see that you have not add flag preserveModules: true in the rollup config file to prevent the build output from bundling. Webpack can not treeshake a bundled file FYI.
export default {
...
preserveModules: true,
...
}
On the side of the project that using it, you have to specify sideEffects in the package.json - read the doc to know how to config them. Beside that, the optimization in webpack has to has sideEffects: true, also read the doc here.
Hope this helps!
As you don't know which components of your Component Library (CL) will be needed by the adopters repositories you need to export everything but in a way
the adopters can execute a tree-shaking on your CL when they do their own build (and just include what they really need).
In a few words, you have to make your CL, tree-shakable. In order to achieve this, on your CL repo you have to:
Use bundlers that support tree-shaking (rollup, webpack, etc..)
Create the build for modules of type es/esm, NOT commonJS/cjs, etc..
Ensure no transpilers/compilers (babel,tsconfig, etc..) usually used as plugins, transform your ES module syntax to another module syntax.
By the default, the behavior of the popular Babel preset #babel/preset-env may break this rule, see the documentation for more details.
// babelrc.json example that worked for me
[
"#babel/preset-env",
{
"targets": ">0.2%, not dead, not op_mini all"
}
],
In the codebase, you always have to use import/export (no require) syntax, and import specifically the things you need only.
import arrayUtils from "array-utils"; //WRONG
import { unique, implode, explode } from "array-utils"; //OK
Configure your sideEffects on the package.json.
"sideEffects": ["**/*.css"], //example 1
"sideEffects": false, //example 2
DO NOT create a single-bundle file but keep the files separated after your build process (official docs don't say this but was the only solution that worked for me)
// rollup.config.js example
const config = [
{
input: 'src/index.ts',
output: [
{
format: 'esm', // set ES modules
dir: 'lib', // indicate not create a single-file
preserveModules: true, // indicate not create a single-file
preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure
sourcemap: true, //optional
},
],
... }]
Additionally, you may need to change your module entry point in order the adopters can directly access to the proper index.js file where you are exporting everthing:
// package.json example
{
...
"module": "lib/index.js", //set the entrypoint file
}
Note: Remember that tree-shaking is executed by an adopter repository that has a build process that supports tree-shaking (eg: a CRA repo) and usually tree-shaking is just executed on prod mode (npm run build), no on dev mode. So be sure to properly test if this is working or not.

Vue.js from Browserify to Webpack

Our current build process currently uses Grunt, vueify, and browserify to build our Single File Components and also pull Vue out of the SFC and into its own external file.
For various reasons (vueify no longer supported, async loading components, ...) we want to switch to Webpack.
However, I am failing at wrapping my head on how to make our current method work for Webpack. I've included our current build process below. I would love to figure out how make Webpack work for us. Any suggestions? I can't even seem to get started... How can I make Webpack compile our *.vue.js files into pre-rendered javascript files? At the bottom I've also included the contents of one of our SFC .vue.js files.
vueRuntime: {
expand: true,
cwd: 'node_modules/vue/dist/',
src: 'vue.runtime.min.js',
dest: 'js/rwd/libs',
ext: '.js',
extDot: 'first',
options: {
configure: b => b
.require('vue')
.transform(
// Required in order to process node_modules files
{global: true},
envify({NODE_ENV: 'production'})
)
.bundle(),
browserifyOptions: {
debug: false
}
}
},
vue: {
expand: true,
cwd: 'js/rwd/',
src: '**/*.vue.js',
dest: 'js/rwd',
ext: '.js',
extDot: 'first',
options: {
configure: b => b
.transform('vueify')
.transform(
// Required in order to process node_modules files
{
global: true
},
envify({NODE_ENV: 'production'})
)
.external('vue')
.bundle(),
browserifyOptions: {
debug: false
}
}
}
A sample *.vue.js file:
const Vue = require('vue');
const App = require('./something/components/Something.vue');
new Vue(App).$mount('#app-element-id');
Having done a similar migration to Vue + Webpack recently, I found this blog post extremely helpful: https://itnext.io/vue-js-and-webpack-4-from-scratch-part-3-3f68d2a3c127
Another source of examples is the vue-cli. Unfortunately, the produced boilerplate is extremely hard to decipher because it requires a ton of node modules that all contribute minuscule amounts of config and also depend on other modules. So if you want to build something customized or actually learn how it all works together, it's more trouble than it's worth.

Webpack Uglify Plugin removes library (Markup.js)

I use webpack to collect and combine all my required JS libraries. I noticed that one of them (Markup.js) is missing from the final minified js file.
After some trial and error I traced the problem to this part of code:
plugins.push(new webpack.optimize.UglifyJsPlugin({
output: {
comments: true, // just for testing
},
compress: {
warnings: false,
},
// skip pre-minified libs
exclude: [/\.min\.js$/gi],
...
If I delete this part, the Markup.js library is part of the final (non-minified) JS file as expected. But when I use the uglify plugin, the Markup.js part is no longer there.
I though this might be because Markup.js is never "used" in the project source code, but using
compress: {
warnings: false,
unused: false,
dead_code: false,
},
makes no difference.
All I want is for the final file to have the same content as before, just minified. The uglify plugin should not make any assumptions about what parts of the code are really "needed".
How can I achieve this?

How to bundle vendor scripts separately and require them as needed with Webpack?

I'm trying to do something that I believe should be possible, but I really can't understand how to do it just from the webpack documentation.
I am writing a JavaScript library with several modules that may or not depend on each other. On top of that, jQuery is used by all modules and some of them may need jQuery plugins. This library will then be used on several different websites which may require some or all modules.
Defining the dependencies between my modules was very easy, but defining their third-party dependencies seems to be harder then I expected.
What I would like to achieve: for each app I want to have two bundle files one with the necessary third-party dependencies and other with the necessary modules from my library.
Example:
Let's imagine that my library has the following modules:
a (requires: jquery, jquery.plugin1)
b (requires: jquery, a)
c (requires: jquery, jquery.ui, a, b)
d (requires: jquery, jquery.plugin2, a)
And I have an app (see it as a unique entry file) that requires modules a, b and c. Webpack for this case should generate the following files:
vendor bundle: with jquery, jquery.plugin1 and jquery.ui;
website bundle: with modules a, b and c;
In the end, I would prefer to have jQuery as a global so I don't need to require it on every single file (I could require it only on the main file, for example). And jQuery plugins would just extend the $ global in case they are required (it is not a problem if they are available to other modules that don't need them).
Assuming this is possible, what would be an example of a webpack configuration file for this case? I tried several combinations of loaders, externals, and plugins on my configuration file, but I don't really get what they are doing and which ones should I use. Thank you!
in my webpack.config.js (Version 1,2,3) file, I have
function isExternal(module) {
var context = module.context;
if (typeof context !== 'string') {
return false;
}
return context.indexOf('node_modules') !== -1;
}
in my plugins array
plugins: [
new CommonsChunkPlugin({
name: 'vendors',
minChunks: function(module) {
return isExternal(module);
}
}),
// Other plugins
]
Now I have a file that only adds 3rd party libs to one file as required.
If you want get more granular where you separate your vendors and entry point files:
plugins: [
new CommonsChunkPlugin({
name: 'common',
minChunks: function(module, count) {
return !isExternal(module) && count >= 2; // adjustable
}
}),
new CommonsChunkPlugin({
name: 'vendors',
chunks: ['common'],
// or if you have an key value object for your entries
// chunks: Object.keys(entry).concat('common')
minChunks: function(module) {
return isExternal(module);
}
})
]
Note that the order of the plugins matters a lot.
Also, this is going to change in version 4. When that's official, I update this answer.
Update: indexOf search change for windows users
I am not sure if I fully understand your problem but since I had similar issue recently I will try to help you out.
Vendor bundle.
You should use CommonsChunkPlugin for that. in the configuration you specify the name of the chunk (e.g. vendor), and file name that will be generated (vendor.js).
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),
Now important part, you have to now specify what does it mean vendor library and you do that in an entry section. One one more item to entry list with the same name as the name of the newly declared chunk (i.e. 'vendor' in this case). The value of that entry should be the list of all the modules that you want to move to vendor bundle.
in your case it should look something like:
entry: {
app: 'entry.js',
vendor: ['jquery', 'jquery.plugin1']
}
JQuery as global
Had the same problem and solved it with ProvidePlugin. here you are not defining global object but kind of shurtcuts to modules. i.e. you can configure it like that:
new webpack.ProvidePlugin({
$: "jquery"
})
And now you can just use $ anywhere in your code - webpack will automatically convert that to
require('jquery')
I hope it helped. you can also look at my webpack configuration file that is here
I love webpack, but I agree that the documentation is not the nicest one in the world... but hey.. people were saying same thing about Angular documentation in the begining :)
Edit:
To have entrypoint-specific vendor chunks just use CommonsChunkPlugins multiple times:
new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),
and then declare different extenral libraries for different files:
entry: {
page1: ['entry.js'],
page2: ['entry2.js'],
"vendor-page1": [
'lodash'
],
"vendor-page2": [
'jquery'
]
},
If some libraries are overlapping (and for most of them) between entry points then you can extract them to common file using same plugin just with different configuration. See this example.
In case you're interested in bundling automatically your scripts separately from vendors ones:
var webpack = require('webpack'),
pkg = require('./package.json'), //loads npm config file
html = require('html-webpack-plugin');
module.exports = {
context : __dirname + '/app',
entry : {
app : __dirname + '/app/index.js',
vendor : Object.keys(pkg.dependencies) //get npm vendors deps from config
},
output : {
path : __dirname + '/dist',
filename : 'app.min-[hash:6].js'
},
plugins: [
//Finally add this line to bundle the vendor code separately
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
new html({template : __dirname + '/app/index.html'})
]
};
You can read more about this feature in official documentation.
Also not sure if I fully understand your case, but here is config snippet to create separate vendor chunks for each of your bundles:
entry: {
bundle1: './build/bundles/bundle1.js',
bundle2: './build/bundles/bundle2.js',
'vendor-bundle1': [
'react',
'react-router'
],
'vendor-bundle2': [
'react',
'react-router',
'flummox',
'immutable'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle1',
chunks: ['bundle1'],
filename: 'vendor-bundle1.js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle2',
chunks: ['bundle2'],
filename: 'vendor-bundle2-whatever.js',
minChunks: Infinity
}),
]
And link to CommonsChunkPlugin docs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Categories