Webpack export function - javascript

I have some js files, and each file is a standonlone function with unique name, And I want to pack all this files in one bundle So I do this code
module.exports = {
entry: {
app:[
'./file1.js',
'./file2.js',
'./file3.js',
'./file4.js'
],
},
output: {
path: './dist/',
filename: '[name].bundle.js'
}
};
that's work and I have my bundle file ".dist/app.bundle.js"
Now I have some js code in the HTML body that need to call functions in the bundle,
If I try to call function "functionA" (that is difined in file1.js) I get this message in browser console
Uncaught ReferenceError: functionA is not defined
The question is how can I export my functions from bundle to import it in my HTML code ?

Exporting things from an entry point file does not make them available to the global scope. You have two options - either explicitly add the functions to the window object, like so:
window.functionA = functionA;
Or configure your build to output as a library:
// webpack.config.js - abridged
module.exports = {
output: {
library: "myModule",
libraryTarget: "umd"
}
};
I don't know how the latter interacts with your entry point being set to an array of files - you may have to make a single entry file (main.js or something) that imports all of the functions and then re-exports them contained in an object, or something like that.

Related

Bundling of independent or dependent js files using webpack and exposing methods to other js files or html <script> code

I have simple three files.
one add.js
function add(x,y){
return x+y
}
two sub.js
function sub(x,y){
return x-y
}
three calc.js
console.log("Add : "+add(5,1))
console.log("Sub : "+sub(5,1))
entry.js
import 'add.js'
import 'sub.js'
import 'main.js'
after webpack when i see.
it tells add method is not found.
my webpack.config
const path = require('path');
module.exports = {
mode:'development',
entry: './entry.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
why the methods are not exposed to main.js.
if i add window.add = add and window.sub = sub then it is exposed, with plain js why the methods are not exposed by main.js.
The purpose of asking is i am having plenty of old js files which as multiple functions and inside html code also these functions are called.
Is there any simple configuration in webpack to expose these function?

How to inject Webpack DefinePlugin variables in non Module Scripts

I have the following issue:
I'm using the DefinePlugin to define some variables across .js modules. This is working fine in .js modules
However, my multi page application loads a local script (custom.js) inside a <HEADER> TAG. This script is standard javascript (not a module), using one of the variables defined in DefinePlugin. It's a .js that must be loaded in every page of the App.
For some reason this variable WEB_CONTEXT is not being interpolated by Webpack on BUILD process.
I assume that the reason is that as it is not recognized as a dependency.
Webpack config.js:
new webpack.DefinePlugin({
'WEB_CONTEXT': 'myapp/main'
);
The global script is loaded like this:
<script src="./src/js/custom.js"></script>
custom.js
$(function () {
// Compiled file incorrectly shows:
const myPath = `${WEB_CONTEXT}/resources/images`;
// Instead of:
const myPath = `myapp/main/resources/images`;
});
});
Question is:
Is there anyway with Webpack to make WEB_CONTEXT variable available ALSO for those scripts like custom.js (not imported, but loaded via ?
Webpack "works" only on files that are part of the dependency tree that starts from the entry file.
If your custom.js file is not inside this tree, webpack won't touch it.
You can add it by require it, or add it as additional entry to your app.
//webpack.config.js
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
custom: './path/to/custom.js'
}
};
webpack.config.js
entry : {
main: './path/to/my/entry/file.js',
custom: './path/to/custom.js'
}
And the in the plugins section:
new HtmlWebpackPlugin({
template: `./${ruta}`,
inject: true,
chunks: ['main', 'custom'], // <-Added custom chunk here.
filename: `${fileName}.html`,
templateParameters: {
WEB_CONTEXT: basePath
}
});

Browserify --standalone with ES6 modules and multiple source files and exports

I am trying to use Gulp, Browserify, and Babelify to compile and transform multiple ES6 files into a single JavaScript library that can be shared with other developers.
I am trying to use multiple ES6 source files, each of which uses ES6 modules using export. I want them all to be wrapped up into a single class/function like a 'namespace'.
It seems like Browserify's --standalone option is designed to do this, but I can only get it to work when there is a single input file. When there are multiple source files with exports, I can't get them all to be included in the 'namespace' class, and I can't control which source file's exports ultimately gets picked to be in the 'namespace' class.
In this example, a.js and b.js are the source files, and I am expecting them to be bundled together in a 'namespace' class called TestModule.
a.js
export function fromA() {
console.log('Hello from a.js');
}
b.js
export function fromB() {
console.log('Hello from b.js');
}
gulpfile.js
const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');
function minimalExample(done) {
return browserify({
entries: [
'./src/a.js',
'./src/b.js'
],
standalone: 'TestModule' // output as a library under this namespace using a umd wrapper
})
.transform('babelify')
.bundle()
.on('error', log.error)
.pipe(source('minimalExample.js'))
.pipe(plumber())
.pipe(gulp.dest('./dist'));
}
module.exports = {
minimalExample
};
What I want
I want minimalExample.js to have an object named TestModule that has functions fromA() and fromB(), so that I can call both methods. I should be able to run either of these commands from the console:
TestModule.fromA()
TestModule.fromB()
What is actually happening
When I load minimalExample.js in a browser, open the console, and inspect the TestModule object, it exists, but it is missing the function from a.js. It only has the function from b.js:
Am I missing a setting somewhere? Is there a way to get Browserify to include all the exports in the standalone 'namespace' class?
Update 1
Prompted by #Zydnar's discussion, I did the obvious thing and actually looked at the output file, minimalExample.js. I don't understand how the transforms are intended to work or what is going wrong yet; I'm still looking at that. But I do see both input files have been transformed and included in the output.
Here is the actual output, and the same thing but pretty-printed by Chrome.
Thanks to help on the browserify project on Github, I have an answer for this. Renée Kooi pointed me in the right direction. They said:
If you have multiple entry points, all of them are executed, but browserify doesn't merge modules, which could cause bad unexpected behaviour.
The solution is to have a single file that acts as an entry point for Browserify that exports everything you want exported. Use that single file as your input source file in the entries option. Browserify will walk your app's dependency tree and include the dependencies it requires. Anything exported by the entry point file will be included in the exported module as expected.
A complete example follows. main.js is the entry point file.
a.js
export function fromA() {
console.log('Hello from a.js');
}
b.js
export function fromB() {
console.log('Hello from b.js');
}
main.js (This one is new)
export * from './a.js';
export * from './b.js';
gulpfile.js
const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');
function minimalExample(done) {
return browserify({
entries: [
'./src/main.js' // THIS LINE HAS CHANGED FROM THE QUESTION
],
standalone: 'TestModule'
})
.transform('babelify')
.bundle()
.on('error', log.error)
.pipe(source('minimalExample.js'))
.pipe(plumber())
.pipe(gulp.dest('./dist'));
}
module.exports = {
minimalExample
};
Now when you run the minimalExample task with gulp, the file generated will have both TestModule.fromA() and TestModule.fromB() functions.

Change domain of images that webpack generates for imported images

Although my dev server is running on localhost:3000, I have set up my host file to point www.mysite.com to localhost. In my JavaScript, I have code like:
import myImage from '../assets/my-image.jpg'
const MyCmp => <img src={myImage} />
Using Webpack's file-loader, it transforms that import into a URL to the hosted image. However, it uses the localhost path to that image, but I'd like it to use the www.mysite.com domain. I looked at both the publicPath and postTransformPublicPath options for file-loader, but those only appear to allow you to modify the part of the path that comes after the domain.
I personally don't like the notion of defining host-information statically in the build output. This is something that should be determined in runtime based on where you actually put your files.
If you are like me then there are two options here.
Both involve you calling a global method that you have defined on i.e. window / global scope.
The purpose of the global method is to resolve the root path (the domain, etc) in runtime.
Define a global method
So lets say you define a method on the global scope somewhere in your startup code like so:
(<any>window).getWebpackBundleRootPath = function (webpackLibraryId) {
if (webpackLibraryId == null) return throw "OOOPS DO SOMETHING HERE!";
// Preferably these variables should be loaded from a config-file of sorts.
if(webpackLibraryId == "yourwebpacklibrary1") return "https://www.yoursite.com/";
// If you have other libraries that are hosted somewhere else, put them here...
return "...some default path for all other libraries...";
};
The next step is to configure webpack to call this global method when it tries to resolve the path.
As I mentioned there are two ways, one that manipulates the output of the webpack and one that is more integrated in webpacks configuration (although only for file-loader but I think it should suffice).
It's worth mentioning that you don't need a global method if you only have one bundle or if you host all your bundles in one place. Then it would be enough to use a global variable instead. It should be quite easy to modify the example below to accommodate this.
First option: configure webpack file-loader to call your method when resolving path
This solution will not require something to be done post build. If this fits your need and covers all scenarios I would go for this option.
Edit your webpack config file
var path = require('path');
let config = {
entry: {
'index': path.join(__dirname, '/public/index.js')
},
output: {
path: path.join(__dirname, '/dist/'),
filename: 'index-bundle.js',
publicPath: 'https://localhost:3000/',
library: 'yourwebpacklibrary1',
...
},
module: {
rules: [{
// Please note that this only defines the resolve behavior for ttf. If you want to resolve other files you need to configure the postTransformPublicPath for those too. This is a big caveat in my opinion and might be a reason for using second option.
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
outputPath: 'assets/fonts', // The folder where you store your fonts.
name: '[name].[ext]',
// This is where the magic happens. This lets you override the output of webpack file resolves.
postTransformPublicPath: function (p) {
// Because of the way webpack file-loader works the input from to this method will look something like this: __webpack_public_path__ + "/assets/fonts/yourfont.ttf"
// But we are not interested in using the __webpack_public_path__ variable so lets remove that.
p = p.replace('__webpack_public_path__ + ', '');
// Return a stringified call to our global method and append the relative path to the root path returned.
return `getWebpackBundleRootPath("${config.output.library}") + ${p}`;
}
}
}]
},
},
...
};
module.exports = config;
As you might have noticed in the comments in the webpack config file you need to specify the resolve behavior for each file-loader that you add (if someone knows a better way, please let me know). This is why I still use the second option.
Second option: manipulate the output of the webpack in a postbuild step
Example webpack.config.js file
For completeness sake here is an example of a webpack.config.js file that contains the variables used in the postbuild script.
var path = require('path');
module.exports = {
entry: {
'index': path.join(__dirname, '/public/index.js')
},
output: {
path: path.join(__dirname, '/dist/'),
filename: 'index-bundle.js',
publicPath: 'https://localhost:3000/',
library: 'yourwebpacklibrary1',
...
},
...
}
Create a postbuild.js file
Create a file postbuild.js next to your package.json with the following content:
const fs = require('fs');
// We take the path to the webpack config file as input so that we can read settings from it.
const webpackConfigFile = process.argv[2];
// Read the webpack config file into memory.
const config = require(webpackConfigFile);
// The file to manipulate is the output javascript bundle that webpack produces.
const inputFile = config.output.path + config.output.filename;
// Load the file into memory.
let fileContent = fs.readFileSync(inputFile, 'utf8');
// Replace the default public path with the call to the method. Please note that if you specify a publicPath as '/' or something very common you might end up with a problem so make sure it is unique in the file to avoid other unrelated stuff being replaced as well.
fileContent = fileContent.replace('"' + config.output.publicPath + '"', 'getWebpackBundleRootPath("' + config.output.library + '")');
// Save the manipulated file back to disk.
fs.writeFileSync(inputFile, fileContent, 'utf8');
Call the postbuild.js automatically on build
Next step is to actually call the postbuild.js script after each build.
This can be done in a postscript in package.json like so (in the script section in your package.json):
{
"scripts": {
"build": "webpack",
"postbuild": "node postbuild.js ./webpack.config.js"
}
}
From now on whenever you run the build script it will also run the postbuild script (from npm or yarn, etc).
You can of course also manually run the postbuild.js script manually after each build instead.
but those only appear to allow you to modify the part of the path that comes after the domain.
Not really, you can give it an URL that includes the domain.
In your case, assuming your images are under the assets directory, you will have something like this in your webpack.config.js
...
module: {
rules: [
...
{
test: /\.(png|jpe?g|gif|svg)$/,
use: {
loader: 'file-loader',
options: {
publicPath: 'https://www.example.com/assets',
outputPath: 'assets'
}
}
},
...
]
}
...

With WebPack, how do I create ready to use "split" bundles with one depending on the other?

My question is very close to others which' answers I believe still require another WebPack-step which I want to avoid. But here is the story first:
I have a Node module (let's call it libfoo) which provides some functionality and requires some third party modules,
and a small script main.js which provides the main entry point and requires libfoo:
main.js:
const foo = require('foo');
function main() {
foo.bar();
}
main();
I now want to turn libfoo and main.js into browser executable deliverables using WebPack. And I want libfoo (which is quite large) to reside statically on the target systems while main.js is very small and changes quickly (just imagine a test scenario where libfoo is a module I want to test and main.js contains changing code snippets)
I managed to create two packages - let's call them foo.browser.js and main.browser.js - which contain all the needed functionality, but I can't manage to make main.browser.js correctly import foo.browser.js.
I'm not very into WebPack yet - and up to now I couldn't figure out what's happening. My current approach looks like this: I build foo.browser.js by running the following command:
webpack --output-filename foo.browser.js foo.js
And I have a webpack.config.js for main.js which looks like this:
module.exports = {
externals: {'foo': 'foo'}, // don't know what I'm doing here - added `commonjs` and `root` randomly
}
I turn main.js into main.browser.js with a very similar command: webpack --output-filename main.browser.js main.js
Now I try to use those both files in file called foo.html containing these lines:
<script src="dist/foo.browser.js"></script>
<script src="dist/main.browser.js"></script>
But when I now open foo.html in a browser I get
external "foo":1 Uncaught ReferenceError: foo is not defined
at Object.foo (external "foo":1)
at __webpack_require__ (bootstrap:19)
at Object../main.js (main.js:3)
at __webpack_require__ (bootstrap:19)
at bootstrap:83
at bootstrap:83
I fiddled around a little but (only randomly I'm afraid) but with no luck.
There is one constraint in my scenario which might be a difference to the other (working) examples I found: I need foo.browser.js and main.browser.js to be "final" i.E. they must run on the target system without any further postprocessing (like running WebPack again to turn them into a single bundle).
You can do it with this type of configuration:
module.exports = [{
resolve: {
modules: ["."],
},
entry: {
"foo": "foo.js",
},
output: {
path: `${__dirname}/build`,
filename: "[name].js",
sourceMapFilename: "[name].js.map",
library: "foo",
// libraryTarget: "umd",
}
},{
resolve: {
modules: ["."],
},
entry: {
"main": "main.js",
},
externals: {
"foo": "foo",
},
output: {
path: `${__dirname}/build`,
filename: "[name].js",
sourceMapFilename: "[name].js.map",
}
}];
This will produce two bundles in the build/ subdirectory. The key to get main to use foo is:
The "foo": "foo" entry in externals for creating the main bundle. Whenever main requests foo it looks for it externally in a "module" named foo. I've put "module" into quotes because when you have bundles in the UMD format and you load them with script, there's no module system. Instead of looking for an actual module, the code will look for a global variable named foo.
The foo bundle exports itself into the global space as the variable foo, which allows it to be used by main.

Categories

Resources