Measure Babel compilation performance (per file or module) - javascript

I'm trying to figure out how to extract some information from babel compilation process.
More specifically, when I run babel (no matter if using Webpack's babel-loader, test frameworks' transformers, Babel's CLI, etc) I'd need to extract some information for each compiled file. Like:
file path
time taken to compile
any other meta data?
What I've tried so far
Speed Measure Plugin for Webpack (link)
Works fine but it provides only Webpack's loaders running time. No info about single compiled files.
Hook into Webpack's compiler/compilation instance
I considered writing a Webpack plugin to hook into the compilation process as described here, but I couldn't find the proper hooks to recognize a file being processed by babel.
Updates
I guess #kidroca pointed out the right direction. More specifically I understand that Babel’s wrapPluginVisitorMethod option is the key for hooking into Babel compilation process.
See babel-minify’s timing plugin.
Related threads:

https://github.com/babel/babel/issues/5340
https://github.com/babel/babel/issues/2206
https://github.com/babel/babel/pull/3659
https://github.com/babel/minify/pull/93
https://github.com/babel/babel/pull/3659
Updates 28/04/18
I eventually tried to wrap a solution into a tool I called babel-timing.

You can use #babel/core and babel.transformSync(code) which will return
Abstract Syntax Tree (AST) information along with some other data. And you can also add some logic to measure the performance of this method
I've setup a minimal repo and played with it myself a little bit: https://github.com/kidroca/babel-meta
Basically you can run npm run analyze-file ./es6-src/es6-module.js or npm run analyze-dir ./es6-src/es6-module.js and checkout the results
This will return:
{
"filename": "/full/path/to/src/file.js",
"cwd": "current/dir",
"ast": "ast information json - lines, comments, and other info",
"executionTime": "execution time in ms",
/* a lot of other info */
}
You can modify the analyze.js file to filter out the info you need
You can modify the .babelrc file to control the transformation and add/remove plugins

Related

can I run a package that is installed on the "client" project from my own project?

I know this sounds like a weird question, let me try explaining it further with examples:
First of all, I'm trying to add some functionality to JSDoc in a simple library. Let's call it jsdoc-extra.
When a project includes my library, it should also have jsdoc installed. I have listed jsdoc as a dependency on my own library as well.
jsdoc-extra > package.json
{
[...]
"dependencies": {
[...]
"jsdoc": "^3.6.6",
[...]
}
}
And let's suppose a "sample" project is trying to use my library (this is what I actually have running for now, installed from the file)
{
[...]
"dependencies": {
"jsdoc": "^3.6.6",
"jsdoc-cov": "file:../jsdoc-cov",
"jsdoc-ts-utils": "^1.1.2"
}
}
From my jsdoc-extra code I can search and find the sample/node_modules/jsdoc/jsdoc.js that is installed on the "client" application (sample), or use my own jsdoc-extra/node_modules/jsdoc/jsdoc.js instead when the first one is not available. I can then execute it with spawn
So far so good. However:
The "client" (sample project in this case) might be using some plugins on their jsdoc setup, like you can see in the previous code snippet, I have ts-utils installed as an example.
So when I'm inside the sample project, and try running:
node_modules/jsdoc/jsdoc-extra.js -c jsdoc.json
(jsdoc.json is the standard jsdoc config file that I just pass through to it)
I get this kind of errors:
(node:3961) UnhandledPromiseRejectionWarning: Error: ERROR: Unable to find the plugin "jsdoc-ts-utils"
It seems my app (jsdoc-extra) cannot use jsdoc-ts-utils that is installed on the client (sample) project, even when I run sample's own installed jsdoc.
I want to be able to execute it like this so the "client" project can execute jsdoc-extra without extra jsdoc configuration, it will use whatever it's already using for regular jsdoc operations.
I'm beginning to think that my best options is to actually write a jsdoc plugin...
I know this is a lot, and probably confusing, I'll gladly provide more info if you think it's necessary. Thanks!
I decided to close this and instead just write a jsdoc plugin. It's the easier way to hack into the data it generates to do what I want. My extra functionality will be tied in with the doc generation, which is not ideal, but I'll deal with it...

Babel hook that runs after all files are processed?

In Babel plugins, there's a post hook, which runs after processing a single JS file. However, I want to run some code after all JS files are processed. Is this possible?
For now, I just added a new plugin which runs after the first plugin, but it'll be cleaner if they're one plugin.
The short answer is that Babel does not provide a hook like this because Babel's core transformation system runs one file at a time. The entire API is:
babel.transform("var foo; /* some code */", { filename: "foo.js" });
so it has no way to register something that would run after a group of files has been processed.
The longer answer is that, if you're using Webpack specifically, you can use Babel in combination with a Webpack plugin to collect metadata from each individual file, and then perform some action with all of that metadata, but that is a Webpack/babel-loader-specific feature. An example of one such Webpack plugin is react-intl-webpack-plugin, which is paired with babel-plugin-react-intl.

How can Babel be used without bundler, but with a dev-experience similar to webpack?

I'm trying to build a new project with ES6 modules without bundling. I still want to use babel-7 to translate TypeScript and JSX to JS. I find it hard to figure out how to set up a development-server for it. I couldn't find any kind of "babel-dev-server" that works similar to webpack-dev-server (hot-module-reloading, browser-sync, file-watcher).
One possibility would be to use browser sync as a static server on e.g. dist and run something like babel src --out-dir dist --watch in parallel. But this excludes hot-reloading and seems a bit clumsy to me. Besides, it would still be useful for build- and dev-steps if you could give the JS-files a hash to control caching better. Or can I configure a build-tool like webpack so that it doesn't perform bundling but still performs some transformations (like putting the hashs in the filenames in imports)?
Prototyping way
A very simple way to do this is to see the server and the transpiling as separate steps
You could use a standalone version of babel as the first script that you load, so you can write jsx inside your html document of javascript files without compiling them.
Simply add on of the cdn links from https://cdnjs.com/libraries/babel-standalone/ as a script like so:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
<script src="/your/jsx/here.js"></script>
<script>
// or here
</script>
</head>
<body>
<div id="application" />
<noscript>This app needs javascript enabled in order to run.</noscript>
</body>
</html>
This would allow you to really quickly prototype things using any webserver that watches files. You can do this using any task runner plugin (i.e. for grunt or gulp) or if you are using visual studio have a look at LiveServer plugin.
When you are moving to production grade you might not want to include the entire babel library. See the other two approaches.
Webpack way
You're asking how to use webpack without bundling. Which can be done using file loader plugin to load every file separately, using a glob pattern. Do make sure whether this is indeed what you need. If all you want is to simply debug your code an relate it back to the original file after compiling, all you need is a standard webpack configuration using bundling and sourcemaps.
Taskrunner way
One way to have even more control over how each file is processed, you can use a taskrunner to do the compile step for you. Below is a simplified example configuration for taskrunner https://gulpjs.com/.
gulpfile.js
const gulp = require('gulp');
const watch = require('gulp-watch');
const webpackStream = require('webpack-stream');
const webpack = require('webpack');
const eslint = require('gulp-eslint');
gulp.task('watch', function() {
return watch('src/**.js', ['compile']);
});
gulp.task('lint', function() {
return gulp.src(['src/*.js', 'src/*/*.js'])
.pipe(eslint({
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true
},
sourceType: 'module'
}
}))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('compile', ['lint'], function() {
return gulp.src('src/main.js')
.pipe(webpackStream({
output: {
filename: 'main.js',
libraryTarget: 'commonjs2',
sourceMapFilename: 'main.js.map',
},
plugins: [],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [
require.resolve('babel-preset-es2015'),
require.resolve('babel-preset-stage-0'),
],
},
},
],
},
}), webpack)
.pipe(gulp.dest('dist/'));
});
This example file can be run, using gulp watch. It'll watch the files for a chance and when it does trigger the other tasks.
I only had an example with webpack, but you can replace it by any other compiler component or even write your own compile step if you want (probably you don't).
This way you have exact control over every step your files go through. Most of which (and more) can also be achieved using the Webpack way. However, it would have the downside of inserting all its boilerplate on top of each processed file, when processing each file as a separate bundle. Ultimately probably something could be done with common chunks plugin.
With the latest release of Snowpack (formerly #pika/web) this should be possible now!
From their website:
TL;DR - With Snowpack you can build modern web apps (using React, Vue, etc.) without a bundler (like Webpack, Parcel, Rollup). No more waiting for your bundler to rebuild your site every time you hit save. Instead, every change is reflected in the browser instantly.
And their "How it Works":
Instead of bundling on every change, just run Snowpack once right after npm install.
Snowpack re-installs your dependencies as single JS files to a new web_modules/ directory. It never touches your source code.
Write code, import those dependencies via an ESM import, and then run it all in the browser.
Skip the bundle step and see your changes reflected in the browser immediately after hitting save.
Keep using your favorite web frameworks and build tools! Babel & TypeScript supported.
check https://www.snowpack.dev/ for more information, they have done a great job with their documentation, it looks really promising!
With webpack and source maps, it shouldn't matter that it changes your code. While this can be a challenge to set up initially, once you get it working you can look at your original source code in the browser debugging tools exactly as they appear to you on disk. The VS Code editor also does a good job of supporting this feature, allowing you to set breakpoints and look at the values of variables directly in your editor without having to use the browser developer tools.
However, if you are still set on trying to get this to work with your original source files then you are right that your ES6 code should just work in most modern browsers
For live reload you could check out the npm livereload package.
Or you could roll your own and figure out how webpack-dev-server does it. They use the chokidar npm package to watch the file system for changes and then they notify the broswer via web sockets. You could probably throw something together that's similar with a little effort.
Here is how webpack-dev-server initiates it:
const watcher = chokidar.watch(watchPath, options);
watcher.on('change', () => {
this.sockWrite(this.sockets, 'content-changed');
});
Obviously there is some JavaScript that runs in the browser waiting on a websocket for that message.
You could use a Webpack plugin like Emit All.

WebPack sourcemaps confusing (duplicated files)

I decided to try out WebPack on a new project I'm spinning up today and I'm getting really strange behavior from the sourcemaps. I can't find anything about it in the documentation, nor can I find anyone else having this issue when skimming StackOverflow.
I'm currently looking at the HelloWorld app produced by Vue-CLI's WebPack template -- no changes have been made to the code, the build environment, or anything.
I installed everything and ran it like so:
vue init webpack test && cd test && npm install && npm run dev
Looking at my sourcemaps, I see the following:
This is a hot mess. Why are there three version of HelloWorld.vue and App.vue? Worse yet, each version has a slightly different version of the code and none of them match the original source. The HellowWorld.vue sitting in the root directory does match the original source, but what's it doing down there instead of in the ./src/components folder? Finally, why isn't there a fourth App.vue that has the original source for it?
As far as I can tell this may have something to do with the WebPack loaders. I've never gotten these kinds of issues with any other bundler, though. Below is an example of the exact same steps using the Browserify Vue-CLI template:
No webpack:// schema, only one copy of every file, the files actually contain the original source code (kind of important for source maps), no unexpected (webpack)/buildin or (webpack)-hot-middleware, no . subdirectory,.... just the source code.
I haven't worked with Vue so can't really describe how exactly this is happening but it seems to be related to Vue Loader. Looking at the documentation I did not really find anything that clarifies why it would create three different files for one component. But it does seem logical considering that a .vue file might contain three types of top-level language blocks: <template>, <script>, and <style>.
Also, looking at two of those files you do see a comment at end of each file that suggests it was modified in some way by a Vue loader. Either this
//////////////////
// WEBPACK FOOTER
// ./node_modules/vue-loader/lib/template-compiler
or
//////////////////
// WEBPACK FOOTER
// ./node_modules/vue-style-loader!./node_modules/css-loader
The third file is different but it still does have code that identifies it as being modified by Vue loader. Here is some of that code
function injectStyle (ssrContext) {
if (disposed) return
require("!!vue-style-loader...")
}
/* script */
import __vue_script__ from "!!babel-loader!../../node_modules/vue-loader/..."
/* template */
import __vue_template__ from "!!../../node_modules/vue-loader/..."
/* styles */
var __vue_styles__ = injectStyle
The document also says this:
vue-loader is a loader for Webpack that can transform Vue components written in the following format into a plain JavaScript module:
Which explains why you might not see the same type of behaviour with other bundlers.
Now, This might not be the answer you were looking for but just wanted to share what I had found.
This is actually a feature of webpack.
webpack has HMR (Hot Module Reloading). If you look in your network tab, go ahead and make an update to your HelloWorld.vue file. You'll see a js chunk come thru as well as an updated JSON manifest. Both of these will have a unique hash at the end for each time you make a change to the application. It does this so the browser does not have to do a full reload.
For a better explanation of this I would highly recommend reading through https://webpack.js.org/concepts/hot-module-replacement/

How to include minified library version in RequireJS optimization

My situation is as follows:
My project based on RequireJS.
I am using RequireJS Optimizer for to create a single JS file.
Some of the module use a certain third party library as a dependency.
The third party is NOT included in the optimized file (libName: empty
in the build config).
RequireJS is configured through var require = {} object which appears
on EACH PAGE, right above the RequireJS. The object defines a path to
the unminifed version of the library, among other things.
What i'd like to achieve:
Use the same config file in both development and production (the require={} object is included with tag on each page). During development I'd like modules to use the UNMINIFIED version of the third party.However, after optimization occurs, i would like all the modules to use the minified version of that third party.
I did think of a solution in theory, but it seems a bit messy and Im hopeful cleaner solution exists:
To have the runtime config point to unminified version
var require = {
paths:{
'thirdParty':'lib/thirdParty'
}
}
Create a module which execute (lets call it "PathRewrite" Module):
requirejs.config({
paths:{
'thirdParty':'lib/thirdParty.min'
}
})
In runtime configuration, define path to "PathRewrite" as empty
var require = {
paths:{
'thirdParty':'lib/thirdParty',
'PathRewrite':'empty'
}
}
In the build configuration file define a real Path to "PathRewrite" in order for it to be included in the "main" file (concatenated file after build).
Include "PathRewrite" as a dependency of a module which is executed first.
What I hope that will happen is that during dev, when optimized file is not used, PathRewrite is will not be used, hence the path to unminified third party in the runtime config will be used.
When the project is optimized, PathRewrite will be included and executed. According to RequireJS documentation, it is possible to run RequireJS configuration twice and the configuration will be appended/overwritten. PathRewrite execution will overwrite the path to "thirdParty" to minified, which will thus be used by all the modules.
Hopefully i've provided enough information. I'd be glad hear of other ways to get this done. Thanks in advance.
This topic appears to have been explored a bit in this answer:
Loading min.js files Generated by TypeScript with Require
Don't let the title discourage you. Typescript is not the core issue of the question being answered there. Unfortunately, the discussion reveals that the RequireJS optimizer may be the only way to get decent minification to work, as it seems incapable of selecting alternate paths properly.
Why don't you want to use inbuilt RequireJs optimizer? You may just include this option
optimize : "uglify2"
and all your and third-party code will be minified after concatenation. In this case you don't need to use minified versions of third-party libraries.

Categories