Code splitting/Lazy load with Webpack & ocLazyLoad - javascript

I know there are solutions out there to retrieve via javascript using System.import, however I want to use the directive version so we don't have to create a controller for every single template.
What I'm trying to achieve is extracting a list of all files sent as an entry file, with a specific extension, and get their bundled name.
Let's say I have 3 files for simplicity:
module-a.lazy.js
module-b.lazy.js
main.entry.js
Let's say my entry points and output are defined like so:
var config = {
entry: {
module-a: "./module-a.lazy.js",
module-b: "./module-b.lazy.js",
bundle: "./main.entry.js"
},
output: {
filename: "[name]-[hash:4].js",
path : '/build'
}
}
Obviously I'm going to end up with 3 files in my build folder, each with a custom dynamic hash in it's filename which i cannot type into the ocLazyLoad directive.
In the main.entry.js file, I have a constant setup, which I'd like to replace with the output names of the lazy files.
angular.module('demo', [])
.constant('lazies', '%lazyfilenamehere%');
Expected output would be something like this:
angular.module('demo', [])
.constant('lazies', ['/build/module-a.lazy-af34.js','/build/module-b.lazy-fdg3.js']);
Once I can obtain the output path names and store them in the main bundle, I can easily decorate the original ocLazyLoad directive to first search this array by a partial string, when matched it can return the whole filename and request it as normal.

You don't even need to specify entrypoints, they will be created automagically once you start using dynamic imports. Use something like that:
angular.module('demo', [])
.constant('lazies', {
'module-a' : () => import('module-a'),
'module-b' : () => import('module-b')
});
Than base your directive on ocLazyLoad and get your lazy module just in time with exact match.
UPD: I started to think probably it's possible to generate a set of directives based on module names. Than you can simply use them anywhere you want!

Related

Webpack loader for file, subfiles and add them to tracking list

I have the map+tilemap project created in a 3rd-party app. The whole project is a set of files, the main file (XML) representing the 2D game level map and some other files (subfiles) representing graphics and tilemaps.
I am trying to create a Webpack Loader that will compile and convert the whole map/tilemap project into JSON object, that is comfortable to use in javascript. And I still can't get how to:
How can I access subfiles (taken from relative paths from the main file), what is the way to access the current directory (where the main file is placed), and the name of the main file?
How can I explain to Webpack to track changes in all subfiles, so it will run the loader again automatically to compile the whole map/tilemap project (partial re-packing).
I spent 2 days to find any working solutions, it is possible at all?
Thanks a lot!
For the first question, webpack loader is expose the file info by this, you can do like this:
module.exports = function (source) {
const loaderContext = this;
const { sourceMap, rootContext, resourcePath } = loaderContext;
const sourceRoot = path.dirname(path.relative(context, resourcePath));
...
}
For the second question, i think that maybe you can use the module.hot to track the subfiles change, like this:
if (module.hot) {
module.hot.accept('filepath', ()=>{
...
})
}

Webpack 4 bundles or ignores dynamic imports

I'm using dynamic imports in index.js:
import('./componentA');
import('./componentB');
import('./componentC');
const myIndexVar = 'My Index Var';
index.ts is the entry point in my webpack.config.js.
The result is a single bundle containing all 4 files - index and the 3 components.
My goal is to have each of the files separately in my dist folder, so that index can load the components dynamically on demand at runtime.
i.e. at runtime I'd like to load index.js, and in turn it'll request components a-c via dynamic imports when needed.
Depending on events yields the same results:
document.body.onclick = () => import('./componentA');
If I try something like this it completely ignores the component and doesn't add it in any way to the dist folder:
let componentName = './componentA';
import(componentName);
I tried following this article:
https://webpack.js.org/guides/code-splitting/
Am I misunderstanding what's supposed to happen?
If I'm not on the right track, is there any alternative that can help me reach my goal?

Workaround to dynamic includes in Pug/Jade

I understand that Pug does not support dynamic includes or extends in templates. Ie
extend path/to/template
works but not
extend #{dynamic_path_to_template}
Is there a workaround (however convoluted) that will allow the same goal of modifying the template used by a view at runtime
Context: My use case is that I am developing an npm module and the template being used to extend other views is located inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested.
I had this issue aswell and found this question while searching for a solution. My solution is similar to Nikolay Schambergs Answer, but i thought i should share it.
I've created a function that renders templates by giving it a path and passed it to the options object. Maybe it helps in your case aswell
const includeFunc = (pathToPug, options = {}) => {
return pug.renderFile(pathToPug, options); //render the pug file
}
const html = pug.renderFile('template.pug', {include: includeFunc});
and then use it as followed in your template:
body
h1 Hello World
|!{include(dynamicPugFilePathFromVariable)}
There is no way to do this for now, but you can work out your application architecture without dynamic extends.
Possible solution #1
Make a layout.jade that conditionally include multiple layouts:
layout.jade:
if conditionalVariable
include firstLayout.jade
else
include otherLayout
In your view, extend layout.jade, and define conditionalVariable in the controller (true/false):
view.jade:
extends layout
block content
p here goes my content!
Possible solution #2
Pass configurations to the layout
- var lang = req.getLocale();
doctype html
block modifyLayout
split the project into multiple entrances, each entrance extends the layout and passes its different configs, and includes different things in different blocks
extends ../layout
block modifyLayout
- var lang = "en" //force language to be en in this page.
block body
include my-page-body
Possible solution #3
use something like terraform which uses pug as its rendering engine, but it enables you to use dynamic partials like this
!= partial(dynamicFileFromVariable)
It works!
First, set res.locals middleware.
middlewares/setResLocals.js
const pug = require('pug')
const path = require('path')
module.exports = function(req, res, next) {
res.locals.include = (pathToPug, options = {}) => { // used for imitate includ pug function
return pug.renderFile(pathToPug, options); //render the pug file
}
res.locals.__PATH__ = path.join(__dirname, '../')
next()
}
server/index.js
app.use(require('../middlewares/setResLocals'))
file.pug
|!{include(`${__PATH__}/${something}`)}
In order to do dynamic include, you will have to use Unescaped String Interpolation, inserting pug contents that are pre-compiled before your main .pug file inside your route. In other words it works as follows:
1) Some .pug files are pre-compiled into HTML
2) The HTML gets fed into another .pug file compilation process
Here's an example how to do it
Inside your router file (routes.js or whatever)
var pug = require('pug')
var html = []
var files = ['file1','file2'] // file names in your views folders
let dir = path.resolve(path.dirname(require.main.filename) + `/app/server/views/`)
//dir is the folder with your templates
app.get('/some-route', (req,res) => {
for (let n = 0; n < files.length; n++) {
let file = path.resolve(dir + '/' + files[n] + `.pug`)
fs.access(file, fs.constants.F_OK, (err) => {
if (!err) {
html.push(pug.renderFile(file, data))
if (n === files.length - 1) {
res.render('dashboard', {html})
}
}
else {
res.status(500).json({code:500,status:"error", error:"system-error"})
}
})
}
})
Inside your desired .pug file:
for item in html
.
!{item}
The example above is specific to my own use case, but it should be easy enough to adapt it.
I know, this is a bit late for answering. But I found a possibility suitable for my purpose by this bit of information from the pug docs:
If the path is absolute (e.g., include /root.pug), it is resolved by
prepending options.basedir. Otherwise, paths are resolved relative to
the current file being compiled.
So, I provide most of my pug modules by relative paths and the stuff I want to exchange dynamically is organised in pug files of the same name but in different folders (think theme) and include them by absolute paths . Then I change the basedir option to dynamically choose a set of pug files (like choosing the theme).
May this help others, too.

webpack 2, angular 2 - using "require" with path built using variable

my issue is this:
whenever I'm using the following syntax inside angular ->
let myCmp = 'test1';
let cmp = require('./components/'+myCmp+'/bootstrapCmp.component.ts');
I'm getting all the components that inside the components folder within my final bundle, not just 'test1';
(I'm using the angular 2 WebPack starter pack of the AngularClass team -
https://github.com/AngularClass/angular-starter
Had anyone this issue too?
the component I'm loading / so is all other ones are basic angular 2 components.
thanks in advance, I'm struggling with it for too much ^^
hope the solution i found for myself will help other developers :
in the webpack.common.js file i've added new file to my entry object which i called : boot.js .
so the structure is like this :
entry: {
main:['./src/polyfills.browser.ts','./src/assets/boot.ts','./src/main.browser.ts']
}
the boot.ts file, contains an object with all the relavent components that i will be using (references).
while the bunlder (wepback) is running, whenever i need some component i'm going to this object i've created inside the boot.ts file, the final bundle file contains now only the components i want.

Webpack plugin to attach static JSON object to output

I am trying to write a webpack plugin that will go into a directory containing html files, open each, remove new lines, and generate an object to attach as a static property to my output file (which is a var).
The object would look something like:
{
htmlFile1: '<p>some html one!</p>',
htmlFile2: '<span>some html two!</span>
}
Then I would like it to be attached to my output like:
App.templates = { .... }
The creation of the object is done. I'm just struggling with webpack and figuring out how to attach it to the main object. Should I just write it to disk and require it? Or is there a way to add it to the bundle via the plugin?
I'm using Rivets.js as a template engine and I have not been able to find anything out there that does something like this already.
Also worth noting, all I'm using is webpack and npm. No Grunt/Gulp/etc
Thanks so much!
Mike
You could use webpack's require.context to import all html files in a directory as text, process the text, and export the results as a single object:
const requireTemplate = require.context('./templates', false, /\.html$/);
module.exports = requireTemplate.keys().reduce((templateMap, templatePath) => {
const templateName = templatePath.match(/\/(\w*?)\.html/)[1]; // get filename without path and extention
templateMap[templateName] = require(templatePath);
return templateMap;
}, {});

Categories