I'm trying to figure out the build process for react native and what exactly it is that both the metro bundler and babeljs do. In particular what allows me to use ES5+ syntax. I'm finding some sources that seem to tell me something different. This source says:
Metro combines all javascript code into a single file and translates
any Javascript code that device wont understand ( like JSX or some
newer javascript syntax )
This one says:
React Native uses Babel to convert React syntax and the newer ES5+
syntax into code that can be run in a JavaScript environment that
doesn’t support those features.
So now i'm confused what exactly both do. Also i've found this in the above source (and metro documentation):
Metro. The transformation process is described as:
All modules go through a transformer. A transformer is responsible for
converting a module to a format that is understandable by the target
platform (eg. React Native). Transformation of modules happens in
parallel based on the amount of cores that you have.
This sounds exactly like the transpilation that babel is supposed to be doing to me, or is this something different? Appart from that i'm confused what the resolution part of the bundling process works and how exactly it works in parralel to the other steps, but maybe that's a topic for another question.
There is a lot of confusion about how everything works together (React-Native, Typescript, Metro, Babel with presets). I will try to describe it.
React-Native is based on ReactJS (UI Framework for developing Web-Apps) which coding language is JavaScript.
JavaScript has some downside and therefore there is the possibility to take advantage of TypeScript. TypesScript is configured by a tsconfig.json file. There you can configure which files need to be transpiled to JavaScript and which should not. If you start e.g. yarn build:ts and have a configured output directory like /dist you will find the transpiled JavaScript code in that directory.
Metro is a JavaScript bundler. It takes in an entry file and various options, and gives you back a single JavaScript file that includes all your code and its dependencies and is started by e.g. yarn start and configured by metro.config.js
Metro has three different stages:
Resolution:
Metro needs to build a graph of all the modules that are required from the entry point. To find which file is required from another file Metro uses a resolver. In reality this stage happens in parallel with the transformation stage.
Transformation:
All modules go through a transformer. A transformer is responsible for converting (transpiling) a module to a format that is understandable by the target platform (eg. React Native). Transformation of modules happens in parallel based on the amount of cores that you have.
Serialization:
As soon as all the modules have been transformed they will be serialized. A serializer combines the modules to generate one or multiple bundles. A bundle is literally a bundle of modules combined into a single JavaScript file.
So, as far as I understand Metro is using Babel in its transformation step. I think that this piece of information is missing in a lot of documentation and explanations. There is no information on that point! Therefore, Babel uses Plugins which tell Babel what to transpile and how to transpile JavaScript Code (e.g. JSX Syntax) from one format to another one.
As you can see in the babel.config.js file, the name of the preset is: metro-react-native-babel-preset, which indicates the usage of babel by the metro transformation process.
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
Related
Scenario:
I'm developing a script using TypeScript (version >= 3). My script has features which dependend upon a well-known graphics library, which itself is written in JavaScript. I have installed this library and its TypeScript type definitions locally in my project, resulting in the relevant files being placed in the appropriate sub-directories of the "node_modules" directory.
I have my project set up to transpile my TypeScript to ES6-specification JavaScript.
I have an import statement at the top of my main TypeScript file of the form:
import * as LIB from 'library'
Where "library.js" is the required library file, burried somewhere in the sub-directories of "node_modules". The TypeScript compiler has its own complex process of module resolution which means that it can find this file without requiring a relative filepath or a file extension.
If I run the TypeScript compiler on my main ".ts" file, I get a JavaScript file out. Because I'm using ES6, the import statement in the generated JavaScript file is identical to the one in my TypeScript source. This is my issue.
The file that the generated JavaScript is attempting to import cannot be found by the script when it runs on my local development server, because the file it is trying to import is somewhere inside the "node_modules" directory and the import is not pointing to that directory. As such, an error along the lines of Error resolving module specifier is shown in the browser console when the code is executed.
I want the code to transpile from TypeScript to JavaScript (ES6) in such a way that the imports in the generated JavaScript can be resolved when I serve my code to a browser from a local test server.
Question:
1) Is this behaviour a result of my improperly configuring the TypeScript compiler?
2) Is this behaviour something that results from or is influenced in a direct way by the properties in the "tsconfig.json" and/or "package.json" configuration files?
3) Is this actually a problem, or is it all caused by my not understanding how files should be organised and served from a Node server?
Thanks in advance for your help.
While you say you are running the code "on a node-based server", your error message suggests that you are actually serving an ES6 module containing a non-relative import like import * as LIB from 'library' to a browser. As you suggest, that isn't supported because the resolution algorithm for non-relative imports in browsers hasn't been standardized yet; see, for example, this article. Either you would need a separate tool to rewrite the non-relative import to a relative import (I'm not familiar with such tools but would invite others to describe them in other answers) or you would need to switch from browser native modules to a non-native module bundler or loader such as Webpack, Rollup, Browserify, or RequireJS, any of which should support your scenario.
At the time of writing, it looks like my problem cannot be solved (in a general way) without using a module bundler or loader as suggested by #Matt McCutchen. Here's a couple of discussions of the issue on the TypeScript GitHub page: 1, 2.
For others finding this question in future, I solved the problem in my specific case a very unsatisfactory way - by avoiding using imports at all, and simply loading the requied library in a HTML script tag before I loaded my main dependent script. This is only acceptable because my project has a single dependency which will not be updated (so I won't ever have to change the "src" in the script tag manually).
I emphasise again, this is not a satisfactory solution in the general case, so I will leave the question open for a while in case anyone comes up with a better answer.
How can you use 2+ typescript files in a browser app?
test.ts:
export class Test
{
}
main.ts
import { Test } from "test";
I know that the browser can't handle the import statements natively. Using angular, it will combine all the files into one JavaScript file that works in the browser.
With specifying an outfile, it will compile to one JavaScript file, but will only work with AMD or the system module, which then doesn't work in the browser (says system or define not defined).
To use more than one file seems difficult compared to Java, AS3, C++, C#. I could do it the traditional JS way and have everything in one, enormous file, but had thought it was possible to have more than one file for a browser project using TypeScript (and know it's possible using Angular or Ionic, which build on top of it).
This isn't job for compiler, especially outFile in tsc has slightly different usage. What you need is bundler, make single chunk of javascript file from each individual modules can be loaded into browser. checkout bundlers like webpack, parcel, rollup, etcs and you can get concepts of how does it work.
Confused as to what the question is, the code to import there looks correct in all but the path name for "test" should be "./test" if its in the same folder structure.
Angular will definitely not combine all the files into one Javascript file that works in the browser that will be your build tool which if you're running the angular seed pretty sure its Webpack.
The official docs for React describe how to generate a production build with webpack using UglifyJS plugin and how to validate that one really is using the production build in a browser. However, I'm using NodeJS 7.x (and hopefully will switch to 8.x soon) and I'm using Babel preset-env, because I don't want to compile ES6 features supported already by NodeJS (e.g. async/await) to ES5. Thus, I can't use UglifyJS plugin, because it doesn't work with ES6 code yet. I tried switching to Babili, but its default preset does some unsafe transformations (see https://github.com/babel/babili/issues/570) that cause my code to break. I can modify the preset to use only safe transformation, but the thing is that I'm not really interested in minifying the code, as it will be executed only on the server; I'm only interested in removing dead code that might eventually slow down the server.
So what's better? Is it better to use a minifier like Babili and configure it to only remove dead code using safe transformations, or maybe it's better to use Webpack Resolve#alias to load production build of React from dist folder?
I have a large project entirely built in JavaScript, I have an ordered and "inside modularized" 5k lines .js file that's the engine of whole site.
Now I have to make other site (extension of this one) in which I'll have to repeat a lot of code, my question is, I've seen lot of possibilities using Browserify, CommonJS, etc. But that's not what I'm searching, I'm searching modularize JavaScript just like C/C++, making #includes with the files of the functions or functionalities and reuse code like that. I'm already doing this including other JS files in HTML, but that JS files are only variables and some arrays, not functionality of the site.
I use jQuery too, in that large 5k lines .js file I have almost all inside the jQuery document.ready event, that's bringing trouble too, because I'll have to make a document.ready event for every file?
I need some orientation please
CommonJS will let you require() modules, this is the foundation for the NodeJS module system. Browserify simplifies this implementation for use in browsers and even allows you to require Node modules (as long as they don't depend on binaries, the file system and other features a browser doesn't support).
var lib = require('someLibrary');
ECMAScript6 (aka: ES6) brings imports to javascript. While browsers don't fully support ES6 yet, you can use Babel to "transpile" ES6 to ES5. This ES5 will take advantage of CommonJS to replicate the importing behaviour.
import { SomeClass, someFunction, someValue } from 'some/library';
In all cases, your javascript will require some kind of pre-processing to transpile it into javscript a browser can understand. This usually means taking all your separate source files and bundling them into a single minified bundle file. This reduces the number of requests the browser has to make.
To handle all this transpiling and bundling, several popular build systems exist including Grunt, Gulp and Webpack. Grunt is older and typically slower because of it's configuration-based design. Gulp is simpler and faster because it relies on NodeJS streams. Webpack is the newest and most powerful, but at the cost of complexity. For what you're hoping to do, I'd recommend looking at Webpack since it can modularize not only your javascript but your stylesheets and other web assets.
http://webpack.github.io/docs/tutorials/getting-started/
Use webpack to bundle your code http://webpack.github.io/docs/tutorials/getting-started/
If I have a node.js application that is filled with many require statements, how can I compile this into a single .js file? I'd have to manually resolve the require statements and ensure that the classes are loaded in the correct order. Is there some tool that does this?
Let me clarify.
The code that is being run on node.js is not node specific. The only thing I'm doing that doesn't have a direct browser equivalent is using require, which is why I'm asking. It is not using any of the node libraries.
You can use webpack with target: 'node', it will inline all required modules and export everything as a single, standalone, one file, nodejs module
https://webpack.js.org/configuration/target/#root
2021 edit: There are now other solutions you could investigate, examples.
Namely:
https://esbuild.github.io
https://github.com/huozhi/bunchee
Try below:
npm i -g #vercel/ncc
ncc build app.ts -o dist
see detail here https://stackoverflow.com/a/65317389/1979406
If you want to send common code to the browser I would personally recommend something like brequire or requireJS which can "compile" your nodeJS source into asynchronously loading code whilst maintaining the order.
For an actual compiler into a single file you might get away with one for requireJS but I would not trust it with large projects with high complexity and edge-cases.
It shouldn't be too hard to write a file like package.json that npm uses to state in which order the files should occur in your packaging. This way it's your responsibility to make sure everything is compacted in the correct order, you can then write a simplistic node application to reads your package.json file and uses file IO to create your compiled script.
Automatically generating the order in which files should be packaged requires building up a dependency tree and doing lots of file parsing. It should be possible but it will probably crash on circular dependencies. I don't know of any libraries out there to do this for you.
Do NOT use requireJS if you value your sanity. I've seen it used in a largish project and it was an absolute disaster ... maybe the worst technical choice made at that company. RequireJS is designed to run in-browser and to asynchronously and recursively load JS dependencies. That is a TERRIBLE idea. Browsers suck at loading lots and lots of little files over the network; every single doc on web performance will tell you this. So you'll very very quickly end up needing a solution to smash your JS files together ... at which point, what's the point of having an in-browser dependency resolution mechanism? And even though your production site will be smashed into a single JS file, with requireJS, your code must constantly assume that any dependency might or might not be loaded yet; in a complex project, this leads to thousands of async load barriers wrapping every interaction point between modules. At my last company, we had some places where the closure stack was 12+ levels deep. All that "if loaded yet" logic makes your code more complex and harder to work with. It also bloats the code increasing the number of bytes sent to the client. Plus, the client has to load the requireJS library itself, which burns another 14.4k. The size alone should tell you something about the level of feature creep in the requireJS project. For comparison, the entire underscore.js toolkit is only 4k.
What you want is a compile-time step for smashing JS together, not a heavyweight framework that will run in the browser....
You should check out https://github.com/substack/node-browserify
Browserify does exactly what you are asking for .... combines multiple NPM modules into a single JS file for distribution to the browser. The consolidated code is functionally identical to the original code, and the overhead is low (approx 4k + 140 bytes per additional file, including the "require('file')" line). If you are picky, you can cut out most of that 4k, which provides wrappers to emulate common node.js globals in the browser (eg "process.nextTick()").