In Webpack, how to use custom source template to construct specific module - javascript

I know some inner functionality inside Webpack. Something about dependencies, template, and module building. However, there is little comment inside its source and no full document site for now. So, i can't chain them all to deal with my problem.
With my current requirement, i need to render specific module with custom source template (similar to this MultiModule in webpack).
Note: To be clear, the generated module's dependency array is not static. For example, one time it may be ['./a', './b', './c'], another time it may be ['./b', './c','./d']. That is up to some dynamic config before build.
For more detail example, i need a module call main.js. In build time, it need to be dynamically generated with target dependencies like(for being not sure which modules would be dependencies):
// main.js
var a = require('./a')
var b = require('./b')
var c = require('./c')
var d = require('./d')
...
In fact, if i only need to dynamically require them all, i can just construct an entry point dynamically.
// webpack.config.js
{
entry: {
main: [
'./a',
'./b',
'./c',
...
]
},
}
and it(webpack) will generate a module may like this:
__webpack_require__(1);
__webpack_require__(2);
__webpack_require__(3);
return __webpack_require__(4);
But i need to do something more:
var a = __webpack_require__(1);
var b = __webpack_require__(2);
var c = __webpack_require__(3);
var d = __webpack_require__(4);
...
// do something with a,b,c,d... under my custom need
...
return somthing or nothing;
As you guys who know about webpack, it's very very complicated and hard to understand and track its plugin(event) hierarchy.
Need some expertise! :)
I'm sorry foy my unclear question before.
However, there is some kind of weird atmosphere. I set up a bounty for attention and guidance. Someone's free-minded answer drove me to make comment with impoliteness somehow. And then some peacemaker shows up with comments unrelated to the question or answer. That sucks.
Focusing on that thing just makes things worse and nothing helped. Not letting it go just means someone has petty mind.

Either lacking attention and lacking expert or not, I have to fight it myself. Fortunately, digging into webpack makes some progress.
Prerequisite
The day before popular webpack, there are fashions like grunt and gulp to construct a custom build flow (with their plugins). They can achieve most of custom requirement, especially generating a custom module(which webpack doesn't have obvious and direct way to deal with).
when you come to do something like automatic collecting custom dependencies, then generating a custom module is the next essential step. It can be commonly seen in product line/family design.
Solutions
#1
This is the simplest and direct way but lack of flexibility.
The source method of MultiModule is to generate the entry module with multi-dependencies. Just overriding it will hit the target.
// hack.js
const MultiModule = require('webpack/lib/MultiModule')
MultiModule.prototype.source = function(dependencyTemplates, outputOptions) {
var str = ['"hello world";\n'];
this.dependencies.forEach(function (dep, idx) {
if (dep.module) {
if (idx === this.dependencies.length - 1)
str.push("module.exports = ");
str.push("__webpack_require__(");
if (outputOptions.pathinfo)
str.push("/*! " + dep.request + " */");
str.push("" + JSON.stringify(dep.module.id));
str.push(")");
} else {
str.push("(function webpackMissingModule() { throw new Error(");
str.push(JSON.stringify("Cannot find module \"" + dep.request + "\""));
str.push("); }())");
}
str.push(";\n");
}, this);
return new RawSource(str.join(""));
}
At the fifth line, i add a string statement "hello world;"\n, nothing else changed.
module.exports = {
entry: {
main: ["./a", "./b"],
}
// something else
}
the output main.js may look like:
//...
/* 0 */
/*!******************!*\
!*** multi main ***!
\******************/
/***/ function(module, exports, __webpack_require__) {
"hello world";
__webpack_require__(/*! ./a */1);
module.exports = __webpack_require__(/*! ./b */2);
/***/ }
//...
Now we can do what we want with the source method, with the compatibility in mind.
#2
This way is much more flexible but also complex.
It requires at lease 5 files(sources are too long, I made them into snippets):
CustomMultiModule.js:
// CustomMultiModule.js
const MultiModule = require('webpack/lib/MultiModule')
const RawSource = require('webpack/lib/RawSource')
class CustomMultiModule extends MultiModule {
constructor(...args) {
super(...args)
}
source(dependencyTemplates, outputOptions) {
var str = ['"hello world";'];
this.dependencies.forEach(function(dep, idx) {
if (dep.module) {
if (idx === this.dependencies.length - 1)
str.push("module.exports = ");
str.push("__webpack_require__(");
if (outputOptions.pathinfo)
str.push("/*! " + dep.request + " */");
str.push("" + JSON.stringify(dep.module.id));
str.push(")");
} else {
str.push("(function webpackMissingModule() { throw new Error(");
str.push(JSON.stringify("Cannot find module \"" + dep.request + "\""));
str.push("); }())");
}
str.push(";\n");
}, this);
return new RawSource(str.join(""));
}
}
module.exports = CustomMultiModule
CustomMultiModuleFactory.js:
// CustomMultiModuleFactory.js
const MultiModuleFactory = require('webpack/lib/MultiModuleFactory')
const CustomMultiModule = require('./CustomMultiModule')
class CustomMultiModuleFactory extends MultiModuleFactory {
constructor() {
super()
}
create(context, dependency, callback) {
callback(null, new CustomMultiModule(context, dependency.dependencies, dependency.name));
};
}
module.exports = CustomMultiModuleFactory
CustomMultiEntryPlugin.js:
// CustomMultiEntryPlugin.js
const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin')
const MultiEntryDependency = require('webpack/lib/dependencies/MultiEntryDependency')
const CustomMultiModuleFactory = require('./CustomMultiModuleFactory')
class CustomMultiEntryPlugin extends MultiEntryPlugin {
constructor(context, entries, name) {
super(context, entries, name)
}
apply(compiler) {
compiler.plugin('after-plugins', function(compiler) {
compiler.plugin("compilation", function(compilation, params) {
var multiModuleFactory = new CustomMultiModuleFactory();
compilation.dependencyFactories.set(MultiEntryDependency, multiModuleFactory);
})
})
}
}
module.exports = CustomMultiEntryPlugin
CustomEntryOptionPlugin.js:
// CustomEntryOptionPlugin.js
const CustomMultiEntryPlugin = require('./CustomMultiEntryPlugin')
class CustomEntryOptionPlugin {
constructor() {}
apply(compiler) {
compiler.plugin("entry-option", function(context, entry) {
if (typeof entry === "object") {
Object.keys(entry).forEach(function(name) {
if (Array.isArray(entry[name])) {
compiler.apply(new CustomMultiEntryPlugin(context, entry[name], name));
}
});
}
});
}
}
module.exports = CustomEntryOptionPlugin
webpack.config.js:
// webpack.config.js
const CustomEntryOptionPlugin = require('./CustomEntryOptionPlugin')
module.exports = {
entry: {
main: ["./a", "/b"] // this dependencies array may be generated
...
},
output: {
path: path.join(__dirname, "js"),
pathinfo: true,
filename: "[name].[chunkhash].js",
chunkFilename: "[chunkhash].js"
}
plugins: [
new CustomEntryOptionPlugin(),
...
]
...
};
With the code above, we can achieve the same as #1. And we can gain more control over the target entry or other requirements, if we want.

Often in webpack you're only requiring one file, and maybe different libs that the files depend on. If you require main, then webpack is going to resolve the dependencies based on the CommonJS syntax which you can read about here. Does removing the extra requirements in your webpack.config.js file solve this? e.g. having only the following as the config:
// webpack.config.js
{
entry: [ "./main" ],
...
}
It sounds like you don't really understand how webpack works-- the idea of it is to emulate how Node's CommonJS syntax allows your javascript to be modular and placed in separate files, while also being performant and not requiring tons of AJAX requests by your browser. If you want to read more about Webpack's config file, check out this page.
As a side note, returning at the end of the module does absolutely nothing. If you want to export, you can use module.exports, but having a line like return true or something at the end of your main.js file doesn't get caught anywhere meaningful.

Related

How to prepend a JavaScript string to every module compiled by Webpack?

I want to prepend a JavaScript string to every module required in my code and compiled by Webpack.
For example, if my entry.js file looks like this:
import _ from 'lodash';
import $ from 'jquery';
import init from './init.js';
init();
I want my output.js bundle to consist of something like this:
function(module, exports, __webpack_require__) {
// prepended JavaScript goes here
// code for lodash module below
}
function(module, exports, __webpack_require__) {
// prepended JavaScript goes here
// code for jquery module below
}
function(module, exports, __webpack_require__) {
// prepended JavaScript goes here
// code for init.js module below
}
function(module, exports, __webpack_require__) {
// prepended JavaScript goes here
var _lodash = __webpack_require__(1);
var _jquery = __webpack_require__(2);
init();
}
I tried writing a simple loader, and it worked, except that it didn't include modules from node_modules since I'm excluding those from my /\.js$/ rule.
So I suspect I need to use a plugin. This is what I have so far:
var pluginName = "PrependJSPlugin";
var apply = (options, compiler) => {
compiler.hooks.compilation.tap(pluginName, compilation =>
compilation.hooks.afterOptimizeModules.tap(pluginName, modules => {
modules.forEach(mod => {
if (mod.resource !== void 0 && mod.resource.includes(".js")) {
var contId = "__WBP_PREPEND__";
var script = `
;var el = document.createElement('pre');
el.innerHTML = '${mod.resource}';
var container = document.getElementById('${contId}');
if (!container) {
container = document.createElement('div');
container.id = '${contId}';
container.appendChild(el);
document.getElementsByTagName('body')[0].appendChild(container);
} else {
container.appendChild(el);
}
`;
mod._source._value = script + mod._source._value;
}
});
})
);
};
module.exports = function(options) {
return {
apply: apply.bind(this, options)
};
};
For the most part, it seems like it's appending it correctly but the compiled output is getting corrupted somehow and I see syntax errors all over the place. I'm definitely doing something I shouldn't be doing with that _source property.
Can someone point me in the right direction?
Why do I want to do this?
This is not something that will go into production. I'm trying to debug a PhantomJS v1 error occurring in a third party service I use for rendering a PDF from an Angular page. Yikes, I know!
I'm catching errors with window.onerror and appending it to the body so they're displayed in the rendered PDF. Unfortunately, the errors don't include source file nor line numbers. So I'm trying to display the name of every module before it runs hoping to see at which one the JavaScript stops executing.

Get Visual Studio Code to give intellisense for custom Javascript imports

I am writing automated testing scripts using TestComplete. TestComplete mostly supports Ecmascript 2015, but it has a few quirks that are causing intellisense to not work as I would expect.
Here is two examples of code files:
File: UsefulStuffFileName.js
class UsefulStuff {
constructor(initValue) {
this.initValue = initValue;
}
get someValue(){
return this.initValue + " someValue";
}
doStuff(input) {
return input + " stuff done";
}
}
module.exports.UsefullStuff = UsefullStuff;
File: WorkingHere.js
var useful = require('UsefulStuffFileName');
class WorkingHere {
constructor() {
this.usefullStuff = new useful.UsefulStuff("Hello");
}
doCoolStuff() {
// I want intellisense options when I type the period after this.usefulStuff
// The options would be someValue, doStuff() and initValue.
// |
// |
// V
let myVariable = this.usefullStuff.someValue;
}
}
The quirks, as I see them are:
The export is done via this style: module.exports.UsefullStuff = UsefullStuff;. (This makes it work with TestComplete.)
The "import" assigns to a variable (var useful = require('UsefulStuffFileName');)
The "new"ing of the object uses the variable to access the class (new useful.UsefulStuff("Hello");).
Is there anyway to configure Visual Studio Code to understand how these files are related and give me intellisense?
Note: If I try the more standard import {UsefulStuff} from './UsefulStuffFileName'; I get an error saying "Unexpected token import" from TestComplete.
This can be done via these steps.
Change the import to look like this:
var { UsefulStuff } = require('UsefulStuff');
Change the instantiation of the class to look like this:
this.usefulStuff = new UsefulStuff("Hello");
Add a file called jsconfig.json and put this in it:
.
{
"compilerOptions": {
"baseUrl": "."
}
}

How to rollup multiple directories separately with one output

So, I have a directory:
mods/
-core/
--index.js
--scripts/
---lots of stuff imported by core/index
This works with typical rollup fashion if you want to rollup to for example mods/core/index.min.js
But I have many of these mods/**/ directories and I want to take advantage of the fact that they are rollup'd into iifes. Each mods/**/index.js will, rather than export, assign to a global variable that we presume is provided:
mods/core/index.js
import ui from './scripts/ui/'
global.ui = ui
mods/someMod/scripts/moddedClass.js
export default class moddedClass extends global.ui.something { /* some functionality extension */}
mods/someMod/index.js
import moddedClass from './scripts/moddedClass'
global.ui.something = moddedClass
So hopefully you can see how each mod directory can be rollup'd in typical fashion but, I need to then put the actual iifes inside another one so that:
mods/compiled.js
(function compiled() {
const global = {};
(function core() {
//typical rollup iife
})();
(function someMod() {
//typical rollup iife
})();
//a footer like return global or global.init()
})();
Any help towards this end would be greatly appreciated. The simplest possible answer, I think, is how I can simply get a string value for each mod's iife instead of rollup writing it to a file.
At that point I could just iterate the /mods/ directory, in an order specified by some modlist.json or something, and call rollup on each /mod/index.js, then build the outer iife myself from strings.
However, I suppose this would not be a full solution for sourcemapping? Or can multiple inline sourcemaps be included? With source mapping in mind, I wonder if another build step might be necessary, where each mod is transpiled before this system even gets to it.
Use rollup's bundle.generate api to generate multiple iifes and write them into one file using fs.appendFile.
For the sourcemaps you can use this module(it's from the same author of rollup) https://github.com/rich-harris/sorcery
Okay so the way I ended up solving this was using source-map-concat
It basically does what I described, right out of the box. The only thing I had to do was to asynchronously iterate the mod directory and rollup each mod, before passing the results to source-map-concat, since rollup.rollup returns a Promise.
I also ended up wanting in-line sourcemaps so that the code can be directly injected rather than written to a file, so I used convert-source-map for that.
The only issue left to solve is sub-source mapping. Sorcery would work great for that, if I was generating files, but I would like to keep it as string sources. For now it will at least show me what mod an error came from, but not the sub-file it came from. If anyone has info on how to do a sorcery-style operation on strings let me know.
Here's the relevant final code from my file:
const rollup = require("rollup")
const concat = require("source-map-concat")
const convert = require("convert-source-map")
const fs = require("fs")
const path = require("path")
const modsPath = path.join(__dirname, "mods")
const getNames = _ => JSON.parse(fs.readFileSync(path.join(modsPath, "loadList.json"), "utf8"))
const wrap = (node, mod) => {
node.prepend("\n// File: " + mod.source + "\n")
}
const rolls = {}
const bundles = {}
const rollupMod = (modName, after) => {
let dir = path.join(modsPath, modName),
file = path.join(dir, "index.js")
rollup.rollup({
entry: file,
external: "G",
plugins: []
}).then(bundle => {
rolls[modName] = bundle.generate({
format: "iife",
moduleName: modName,
exports: "none",
useStrict: false,
sourceMap: true
})
after()
})
}
const rollupMods = after => {
let names = getNames(), i = 0,
rollNext = _ => rollupMod(names[i++], _ => i < names.length - 1? rollNext() : after())
rollNext()
}
const bundleCode = after => {
rollupMods(_ => {
let mods = concat(getNames().map(modName => {
let mod = rolls[modName]
return {
source: path.join(modsPath, modName),
code: mod.code,
map: mod.map
}
}), {
delimiter: "\n",
process: wrap
})
mods.prepend("(function(){\n")
mods.add("\n})();")
let result = mods.toStringWithSourceMap({
file: path.basename('.')
})
bundles.code = result.code + "\n" + convert.fromObject(result.map).toComment()
after(bundles.code)
})
}
exports.bundleCode = bundleCode

How do I require() from the console using webpack?

How do I require() / import modules from the console? For example, say I've installed the ImmutableJS npm, I'd like to be able to use functions from the module while I'm working in the console.
Here's another more generic way of doing this.
Requiring a module by ID
The current version of WebPack exposes webpackJsonp(...), which can be used to require a module by ID:
function _requireById(id) {
return webpackJsonp([], null, [id]);
}
or in TypeScript
window['_requireById'] =
(id: number): any => window['webpackJsonp'];([], null, [id]);
The ID is visible at the top of the module in the bundled file or in the footer of the original source file served via source maps.
Requiring a module by name
Requiring a module by name is much trickier, as WebPack doesn't appear to keep any reference to the module path once it has processed all the sources. But the following code seems to do the trick in lot of the cases:
/**
* Returns a promise that resolves to the result of a case-sensitive search
* for a module or one of its exports. `makeGlobal` can be set to true
* or to the name of the window property it should be saved as.
* Example usage:
* _requireByName('jQuery', '$');
* _requireByName('Observable', true)´;
*/
window['_requireByName'] =
(name: string, makeGlobal?: (string|boolean)): Promise<any> =>
getAllModules()
.then((modules) => {
let returnMember;
let module = _.find<any, any>(modules, (module) => {
if (_.isObject(module.exports) && name in module.exports) {
returnMember = true;
return true;
} else if (_.isFunction(module.exports) &&
module.exports.name === name) {
return true;
}
});
if (module) {
module = returnMember ? module.exports[name] : module.exports;
if (makeGlobal) {
const moduleName = makeGlobal === true ? name : makeGlobal as string;
window[moduleName] = module;
console.log(`Module or module export saved as 'window.${moduleName}':`,
module);
} else {
console.log(`Module or module export 'name' found:`, module);
}
return module;
}
console.warn(`Module or module export '${name}'' could not be found`);
return null;
});
// Returns promise that resolves to all installed modules
function getAllModules() {
return new Promise((resolve) => {
const id = _.uniqueId('fakeModule_');
window['webpackJsonp'](
[],
{[id]: function(module, exports, __webpack_require__) {
resolve(__webpack_require__.c);
}},
[id]
);
});
}
This is quick first shot at this, so it's all up for improvement!
Including this in a module will allow require([modules], function) to be used from a browser
window['require'] = function(modules, callback) {
var modulesToRequire = modules.forEach(function(module) {
switch(module) {
case 'immutable': return require('immutable');
case 'jquery': return require('jquery');
}
})
callback.apply(this, modulesToRequire);
}
Example Usage:
require(['jquery', 'immutable'], function($, immutable) {
// immutable and $ are defined here
});
Note: Each switch-statement option should either be something this module already requires, or provided by ProvidePlugin
Sources:
Based on this answer, which can be used to add an entire folder.
Alternative method from Webpack Docs - which allows something like require.yourModule.function()
I found a way that works, for both WebPack 1 and 2. (as long as the source is non-minified)
Repo: https://github.com/Venryx/webpack-runtime-require
Install
npm install --save webpack-runtime-require
Usage
First, require the module at least once.
import "webpack-runtime-require";
It will then add a Require() function to the window object, for use in the console, or anywhere in your code.
Then just use it, like so:
let React = Require("react");
console.log("Retrieved React.Component: " + React.Component);
It's not very pretty (it uses regexes to search the module wrapper functions) or fast (takes ~50ms the first call, and ~0ms after), but both of these are perfectly fine if it's just for hack-testing in the console.
Technique
The below is a trimmed version of the source to show how it works. (see the repo for the full/latest)
var WebpackData;
webpackJsonp([],
{123456: function(module, exports, __webpack_require__) {
WebpackData = __webpack_require__;
}},
[123456]
);
var allModulesText;
var moduleIDs = {};
function GetIDForModule(name) {
if (allModulesText == null) {
let moduleWrapperFuncs = Object.keys(WebpackData.m).map(moduleID=>WebpackData.m[moduleID]);
allModulesText = moduleWrapperFuncs.map(a=>a.toString()).join("\n\n\n");
// these are examples of before and after webpack's transformation: (which the regex below finds the var-name of)
// require("react-redux-firebase") => var _reactReduxFirebase = __webpack_require__(100);
// require("./Source/MyComponent") => var _MyComponent = __webpack_require__(200);
let regex = /var ([a-zA-Z_]+) = __webpack_require__\(([0-9]+)\)/g;
let matches = [];
let match;
while (match = regex.exec(allModulesText))
matches.push(match);
for (let [_, varName, id] of matches) {
// these are examples of before and after the below regex's transformation:
// _reactReduxFirebase => react-redux-firebase
// _MyComponent => my-component
// _MyComponent_New => my-component-new
// _JSONHelper => json-helper
let moduleName = varName
.replace(/^_/g, "") // remove starting "_"
.replace(new RegExp( // convert chars where:
"([^_])" // is preceded by a non-underscore char
+ "[A-Z]" // is a capital-letter
+ "([^A-Z_])", // is followed by a non-capital-letter, non-underscore char
"g"),
str=>str[0] + "-" + str[1] + str[2] // to: "-" + char
)
.replace(/_/g, "-") // convert all "_" to "-"
.toLowerCase(); // convert all letters to lowercase
moduleIDs[moduleName] = parseInt(id);
}
}
return moduleIDs[name];
}
function Require(name) {
let id = GetIDForModule(name);
return WebpackData.c[id].exports;
}
Being able to use require modules in the console is handy for debugging and code analysis. #psimyn's answer is very specific so you aren't likely to maintain that function with all the modules you might need.
When I need one of my own modules for this purpose, I assign a window property to it so I can get at it e.g window.mymodule = whatever_im_exporting;. I use the same trick to expose a system module if I want to play with it e.g:
myservice.js:
let $ = require('jquery');
let myService = {};
// local functions service props etc...
module.exports = myService;
// todo: remove these window prop assignments when done playing in console
window.$ = $;
window.myService = myService;
It is still a bit of a pain, but digging into the bundles, I can't see any way to conveniently map over modules.
The answer from #Rene Hamburger is good but unfortunately doesn't work anymore (at least with my webpack version). So I updated it:
function getWebpackInternals() {
return new Promise((resolve) => {
const id = 'fakeId' + Math.random();
window['webpackJsonp'].push(["web", {
[id]: function(module, __webpack_exports__, __webpack_require__) {
resolve([module, __webpack_exports__, __webpack_require__])
}
},[[id]]]);
});
}
function getModuleByExportName(moduleName) {
return getWebpackInternals().then(([_, __webpack_exports__, __webpack_require__]) => {
const modules = __webpack_require__.c;
const moduleFound = Object.values(modules).find(module => {
if (module && module.exports && module.exports[moduleName]) return true;
});
if (!moduleFound) {
console.log('couldnt find module ' + moduleName);
return;
}
return moduleFound.exports[moduleName];
})
}
getModuleByExportName('ExportedClassOfModule');
expose-loader is, in my opinion, a more elegant solution:
require("expose-loader?libraryName!./file.js");
// Exposes the exports for file.js to the global context on property "libraryName".
// In web browsers, window.libraryName is then available.
Adding the below code to one of your modules will allow you to load modules by id.
window.require = __webpack_require__;
In the console use the following:
require(34)
You could do something similar as psimyn advised by
adding following code to some module in bundle:
require.ensure([], function () {
window.require = function (module) {
return require(module);
};
});
Use require from console:
require("./app").doSomething();
See more
After making an npm module for this (see my other answer), I did a search on npms.io and seem to have found an existing webpack-plugin available for this purpose.
Repo: https://www.npmjs.com/package/webpack-expose-require-plugin
Install
npm install --save webpack-expose-require-plugin
Usage
Add the plugin to your webpack config, then use at runtime like so:
let MyComponent = require.main("./path/to/MyComponent");
console.log("Retrieved MyComponent: " + MyComponent);
See package/repo readme page for more info.
EDIT
I tried the plugin out in my own project, but couldn't get it to work; I kept getting the error: Cannot read property 'resource' of undefined. I'll leave it here in case it works for other people, though. (I'm currently using the solution mentioned above instead)
After both making my own npm package for this (see here), as well as finding an existing one (see here), I also found a way to do it in one-line just using the built-in webpack functions.
It uses WebPack "contexts": https://webpack.github.io/docs/context.html
Just add the following line to a file directly in your "Source" folder:
window.Require = require.context("./", true, /\.js$/);
Now you can use it (eg. in the console) like so:
let MyComponent = Require("./Path/To/MyComponent");
console.log("Retrieved MyComponent: " + MyComponent);
However, one important drawback of this approach, as compared to the two solutions mentioned above, is that it seems to not work for files in the node_modules folder. When the path is adjusted to "../", webpack fails to compile -- at least in my project. (perhaps because the node_modules folder is just so massive)

RequireJS: how to add a prefix to path

I'm developing a SPA using Knockout.js V3 and RequireJS.
I have ko components written like this:
define(['text!settings.html'],
function( htmlString) {
'use strict';
function SettingsViewModel(params) {
...
}
// Return component definition
return {
viewModel: SettingsViewModel,
template: htmlString
};
});
Now i want to support localization and for that I have duplicated html for each supported language, so for example:
en/settings.html
de/settings.html
se/settings.html
I would like to let the user change a language and refresh the app with the new language, is it possible to instruct require text plugin to add the language prefix to all html, so when i write:
text!settings.html
it will actually load:
text!de/settings.html
Not sure if you can let the text plugin prefix the urls. What you might be able to do is create a custom template loader:
var templateFromLanguageUrlLoader = {
loadTemplate: function(name, templateConfig, callback) {
if (templateConfig.languageUrl) {
// Language from config or default language
var lang = templateConfig.lang || 'de';
var fullUrl = lang + '/' + templateConfig.languageUrl;
$.get(fullUrl, function(markupString) {
ko.components.defaultLoader.loadTemplate(name, markupString, callback);
});
} else {
// Unrecognized config format. Let another loader handle it.
callback(null);
}
}
};
// Register it
ko.components.loaders.unshift(templateFromLanguageUrlLoader );
Then your component would look something like this:
define([],
function() {
'use strict';
function SettingsViewModel(params) {
...
}
// Return component definition
return {
viewModel: SettingsViewModel,
template: {
languageUrl: 'settings.html',
language: 'nl' // overwrite default
}
};
});
Eventually i went with a different solution, i just added a variable to the path:
define(['text!' + globals.bundlePath + 'settings.html']
This variable is initialized from session storage (and get's a default if nothing found) and so when a user changes language, i keep it in session storage and refresh the app, and that way the pages are now loaded with the new language.

Categories

Resources