webpack2 dynamic style file require - javascript

I am working with webpack 2 and I want to include some style file that depends from different NODE_ENV.
I did something like this:
const stylesEntryName = process.env.SECOND_CLIENT ? "main_for_second_client" : "main";
const entryUrl = `assets/styles/${stylesEntryName}.styl`;
console.log("stylesEntryName ====>>> ", stylesEntryName, entryUrl);
require(entryUrl);
But it isn't working somehow. I have got an error: Critical dependency: the request of a dependency is an expression
the console shows: stylesEntryName ====>>> main assets/styles/main.styl
Maybe I do something wrong? (in case of direct url)
require('assets/styles/main.styl');
the code working fine.
Thanks for any help.

webpack is emitting that warning about the line require(entryUrl) because the value of the entryUrl variable is unknown until the code is executed (i.e., the require is not statically analyzable). Here's the webpack documentation about that: https://webpack.github.io/docs/context.html#critical-dependencies
You can fix this problem by removing the dynamic require, and instead use if-else statements to pick a static require statement out of the possible choices. Here's code that does that for your issue:
if (process.env.SECOND_CLIENT === 'main_for_second_client') {
require('assets/styles/main_for_second_client.styl')
} else {
require('assets/styles/main.styl')
}

Related

Why do I see TypeScript in Vue.js source code?

I went through the vue.js source code just to have a look, and I saw some strange code, which I learnt was TypeScript syntax after some research. My problem is, this syntax is in a ".js" file, which I don't understand because I know TypeScript files (.ts) should compile to pure JS. So why do I still see the type annotation in the function parameter in a .js file?
function hasAncestorData (node: VNode) {
const parentNode = node.parent
return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
}
This is actually a Flow code. You can see the /* #flow */ comment at the beginning of some files that enables the tool's type checking. It's a bit similar to TypeScript, but those are not the same things.
A quick look through the src folder of the Vue.js github repo shows that they do indeed use .js for their JavaScript w/Flow code, for instance in src/core/vdom/create-component.js:
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
But if we look in the dist folder, we can see that those Flow type annotations have been removed for distribution. For instance, here's the above in dist/vue.js (that line number will rot over time):
var componentVNodeHooks = {
init: function init (vnode, hydrating) {

ES6 Dynamic Imports using Webpack and Babel

I've been using Webpack for my ES6 JS project and has been going well until I started to play with dynamic imports.
What I had that worked (router.js):
import { navigo } from "Navigo"; // router
import { clients } from "Controllers/clients.js";
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
clients.init();
}
});
But the more pages/routes I add, the more imports get stacked up in the head of the module. This is a relatively large app and I have a lot of pages/routes to add and therefore I need to load them dynamically to reduce the size of the initial page load.
So, following Webpack's documentation for dynamic imports, I tried the following which loads the controller module only when the relative route is called:
import { navigo } from "Navigo"; // router
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
import("Controllers/clients.js").then((clients) => {
clients.init();
});
}
});
But saving this in my editor resulted in a Babel transpiling error; SyntaxError: 'import' and 'export' may only appear at the top level, and clients.init() is not being called when tested in browser.
After a bit of reading, I discovered I needed a Babel plugin to transpile dynamic import() to require.ensure. So, I installed the plugin using the following command:
npm install babel-plugin-dynamic-import-webpack --save-dev
And declared the plugin in my babel.rc file
{ "plugins": ["dynamic-import-webpack"] }
After installing the plugin, the transpiling error disappeared and checking my transpiled code I found that the dynamic import()s has in fact been changed to require.ensure as expected. But now I get the following browser errors when testing:
Error: Loading chunk 0 failed.
Stack trace:
u#https://<mydomain.com>/js/app.bundle.js:1:871
SyntaxError: expected expression, got '<' 0.app.bundle.js:1
Error: Loading chunk 0 failed.
I didn't understand why it was referencing 0.app.bundle.js with the 0. prefix, so I checked my output/dist folder and I now have a new file in there called 0.app.bundle.js:
0.app.bundle.js 1,962bytes
app.bundle.js 110,656bytes
I imagine this new bundled file is the dynamically imported module, clients.js.
I only added dynamic importing to that one route and have left all the other routes as they were. So, during testing, I can view all routes except that one /clients route that now throws the above errors.
I'm totally lost at this point and hoped somebody could help push me over the finish line. What is this new file 0.app.bundle.js and how am I supposed to be using it/including it in my application?
I hope I've explained myself clearly enough and look forward to any responses.
I managed to fix my own problem in the end, so I will share what I discovered in an answer.
The reason the chunk file wasn't loading was because Webpack was looking in the wrong directory for it. I noticed in the Network tab of my developer console that the the chunk file/module was being called from my root directory / and not in /js directory where it belongs.
As per Webpack's documentation, I added the following to my Webpack config file:
output: {
path: path.resolve(__dirname, 'dist/js'),
publicPath: "/js/", //<---------------- added this
filename: 'app.bundle.js'
},
From what I understand, path is for Webpack's static modules and publicPath is for dynamic modules.
This made the chunk load correctly but I also had further issues to deal with, as client.init() wasn't being called and yielded the following error:
TypeError: e.init is not a function
To fix this, I also had to change:
import("Controllers/clients.js").then((clients) => {
clients.init();
});
To:
import("Controllers/clients.js").then(({clients}) => {
clients.init();
});
Note the curly braces in the arrow function parameter.
I hope this helps somebody else.
For debugging, you need to do
import("Controllers/clients.js").then((clients) => {
console.log(clients);
});
maybe working
import("Controllers/clients.js").then((clients) => {
clients.default.init();
});

Angular2 with TypeScript: how does exporting work?

I have been going through the Angular2 tutorial https://angular.io/docs/ts/latest/tutorial/ . I have cloned the code to it from https://github.com/johnpapa/angular2-tour-of-heroes
I am trying to understand how the TypeScript is working. When you export a function in TypeScript, say called 'fnc', I see in the js file that gets generated this is the equivalent to doing exports.fnc = fnc.
This is a Node thing to export JavaScript in this way so how does this code then work on the frontend when running in a browser and not Node? Is there another package that has to be installed therefore to use TypeScript on the frontend that are then also changing these js files that generated again.
When I look at the code in the Developer Tools I see it has now been wrapped in a
(function(System, SystemJS) {(function(require, exports, module, __filename, __dirname, global, GLOBAL) {"use strict"
...
}).apply(__cjsWrapper.exports, __cjsWrapper.args);
})(System, System);
What has changed the code to look like this. Is this a TypeScript specific thing that has been done here or something specific in just the Angular application I have used?
Lastly, to incorporate JavaScript modules into my application that is not TypeScript, is the right thing to do just put export.something = something for everything I want to export?
You might want to read TypeScript's docs. Here's a link to their modules page : https://www.typescriptlang.org/docs/handbook/modules.html.
TypeScript isn't readable by some browsers, so Angular 2's code needs to be compiled into browser readable code ( usually ES5 ). This is done through webpack, gulp, browserify, etc.. There are many different tools that allow for this transpiling.
I suggest that if you're working on a TypeScript application, don't try to use vanilla JavaScript along side TypeScript, compilers don't usually like this. But, to answer your question, in node you export using modules.exports https://nodejs.org/api/modules.html#modules_module_exports
The wrapping you are seeing is not related to Typescript. Your script was loaded by SystemJS, and was wrapped before presenting it to the browser. You can see this in the SystemJS function for getSource:
return (wrap ? '(function(System, SystemJS) {' : '') + source + (wrap ? '\n})(System, System);' : '')
// adds the sourceURL comment if not already present
+ (source.substr(lastLineIndex, 15) != '\n//# sourceURL='
? '\n//# sourceURL=' + address + (sourceMap ? '!transpiled' : '') : '')
// add sourceMappingURL if load.metadata.sourceMap is set
+ (sourceMap && inlineSourceMap(sourceMap) || '');
To your question about how CommonJS modules can be loaded by the browser, that's a feature of SystemJS. The call to apply at the end is specific to CommonJS module handling:
case 'cjs':
...
source = cjsWrapper + ") {" + source.replace(hashBangRegEx, '') + "\n}).apply(__cjsWrapper.exports, __cjsWrapper.args);";

Browserify: Uncaught error when requiring js file by path created from string concatenation

I am using browesify for client side app. I have files maps.js and directions.js sitting besides my index.js file, and inside my index.js, I have a function getPageName() which returns the name of the file that I should "require". When I try to do
var pageName = getPageName();
var page;
if (pageName === 'maps') {
page = require('./maps');
} else if (pageName === 'directions') {
page = require('./directions');
}
it works fine. But when I instead try to use following trick to minimize my code,
var pageName = getPageName();
var page = require('./' + pageName);
I get error Uncaught Error: Cannot find module './maps' in console log in Chrome. Why does it not work when I use string concatenation but works when I use pass the path explicitly?
That is a limitation of Browserify and similar type libraries. In order to do the require() the library has to examine the js code as a string, and determine file paths before the code is run. This means file paths have to be static in order for it to be picked up.
https://github.com/substack/node-browserify/issues/377
Browserify can only analyze static requires. It is not in the scope of
browserify to handle dynamic requires

require('template.jade') in react-starter-kit

Can someone maybe explain me, how this build-time require works?
https://github.com/kriasoft/react-starter-kit/blob/feature/redux/src/server.js#L89
They are requiring a jade template, which package or configuration allows this, I seem unable to find it myself.
const template = require('./views/index.jade')
I think is much more elegant then:
import jade from 'jade'
const template = jade.compile('./views/index.jade')
As RGraham mentioned in his comment, the require call is being "intercepted" during webpack's compilation of the application bundle. This is done using "loaders" that define particular behaviour for imports of a particular type:
Loaders allow you to preprocess files as you require() or “load” them.
In this particular case, the loader that does this modification could be one of these (or another that I didn't find in my search):
https://github.com/bline/jade-html-loader
https://github.com/webpack/jade-loader
Edit: looking at the project's own webpack configuration we can see it is the second link above:
{
test: /\.jade$/,
loader: 'jade-loader',
}
jade-loader reads the content of the specified file, which make look something like this (Jade string):
h1 Hello, #{author}!
..and replaces that with a CommonJS JavaScript code similar to this (at compile time):
module.exports = function(data) {
return `<h1>Hello, ${data.name}</h1>`;
};

Categories

Resources