I'm currently using Snowpack to build/prepare an application. I'm trying to import a JavaScript library I installed as part of the dependencies (block) in the package.json file, but somehow Snowpack is not picking up the library.
This is (an excerpt with the relevant content of) the package.json file:
{
"private": true,
"type": "module",
"scripts": {
"build": "snowpack build",
"preview": "snowpack dev"
},
"dependencies": {
"impress.js": "1.1.0"
},
"devDependencies": {
"snowpack": "3.3.7"
},
"engines": {
"node": "14.x"
}
}
The snowpack.config.js only contains these lines:
/** #type {import("snowpack").SnowpackUserConfig } */
export default {
devOptions: {
open: "none",
},
mount: {
src: {
url: "/",
},
},
};
I was expecting Snowpack to bundle the impress.js library with this HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<script src="node_modules/impress.js/js/impress.min.js"></script>
<script src="scripts/global.js" type="module"></script>
</body>
</html>
Is there a way to configure Snowpack for such things?
If you simply want to convert the Impress module to ESM have you considered trying esinstall?
i.e.
convert.mjs:
import {install} from 'esinstall';
await install(['impress.js'], {});
node convert.mjs
and then in your HTML file:
<script src="web_modules/impress.js"></script>
In case somebody come across something similar to this, the way I found to bundle Impress.js (and probably any other library that has/hasn't been modularized) is to just import it in the JavaScript file (notice the type="module" in the HTML script tag):
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<script src="scripts/global.js" type="module"></script>
</body>
</html>
import "impress.js";
impress(); // ...bootstraps/initializes Impress.js
Related
tl;dr: (how) can I use an ES6 module that imports other modules' exports, in a VS Code extension creating a Webview?
Situation
I'm trying to update and improve a VS Code extension first written 4 years ago. The extension creates a webview, using HTML and JavaScript modules. This is the code that used to work:
<head>
<!-- ... -->
<script type='module'>
'use strict';
import { loadFromString as loadSCXML } from '${scxmlDomJs}';
import SCXMLEditor from '${scxmlEditorJs}';
import NeatXML from '${neatXMLJs}';
// …
</script>
</head>
…where the contents of the ${…} strings were replaced with URIs generated via:
path.join(extensionContext.extensionPath, 'resources', 'scxmlDOM.js')
These days the Webview in VS Code is now locked down for security, and (as I understand it) I need to replace the inline <script> element with something like the following:
<head>
<!-- ... -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'">
<script nonce="${nonce}" src="${mainJS}"></script>
where mainJS is a path like above, further wrapped in a call to webview.asWebviewUri(…).
The Question
If I move my module code into a separate file main.js, how can it import other modules, when the paths to those modules need to be generated?
I've found several working examples (including the one linked above) for how to make script in webviews work with CORS and nonces, but I cannot find a resource on how to make it work when those scripts are modules. The closest I've found is this question which only might be related, but which is also unanswered.
One solution that works (tested) is to use an import map to map simple names to the URI in the HTML, and modify the main.js to import by the simple names.
Webview HTML:
<head>
<!-- ... -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'">
<script nonce="${nonce}" type="importmap">
{
"imports": {
"scxmlDOM": "${scxmlDOMJS}",
"scxmlEditor": "${scxmlEditorJS}",
"neatXML": "${neatXMLJS}"
}
}
</script>
<script nonce="${nonce}" type="module" src="${mainJS}"></script>
main.js:
'use strict';
import { loadFromString as loadSCXML } from 'scxmlDOM';
import SCXMLEditor from 'scxmlEditor';
import NeatXML from 'neatXML';
// …
I don't know if the nonce is strictly needed on the import map <script> element, but it certainly works with it present.
Note that the ${…} URIs are not literals, but expected to be replaced with the output from the asWebviewUri() function.
In my extension vscode-antlr4 I don't use an import map. Instead I set up my project such that for the webview contents I have an own tsconfig.json file which causes tsc to produce ES2022 modules (while for the extension itself CommonJS is used).
{
"compilerOptions": {
"declaration": true,
"module": "ES2022",
"target": "ES2022",
"outDir": "../../out",
"removeComments": true,
"noImplicitAny": true,
"inlineSources": true,
"inlineSourceMap": true,
"isolatedModules": false,
"allowSyntheticDefaultImports": true,
"allowUmdGlobalAccess": true, // For D3.js
"moduleResolution": "node",
"experimentalDecorators": true,
"strictNullChecks": true,
"alwaysStrict": true,
"composite": true,
"rootDir": "../.."
},
"include": [
"./*.ts"
],
"exclude": []
}
This setup allows me to import 3rd party libs, like antlr4ts and d3 from the node_modules folder. I can now import these webview scripts in my webview content code like shown for example in the railroad diagram provider.
public generateContent(webview: Webview, uri: Uri, options: IWebviewShowOptions): string {
const fileName = uri.fsPath;
const baseName = basename(fileName, extname(fileName));
const nonce = this.generateNonce();
const scripts = [
FrontendUtils.getMiscPath("railroad-diagrams.js", this.context, webview),
];
const exportScriptPath = FrontendUtils.getOutPath("src/webview-scripts/GraphExport.js", this.context,
webview);
if (!this.currentRule || this.currentRuleIndex === undefined) {
return `<!DOCTYPE html>
<html>
<head>
${this.generateContentSecurityPolicy(webview, nonce)}
</head>
<body><span style="color: #808080; font-size: 16pt;">No rule selected</span></body>
</html>`;
}
let diagram = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
${this.generateContentSecurityPolicy(webview, nonce)}
${this.getStyles(webview)}
<base href="${uri.toString(true)}">
<script nonce="${nonce}">
let graphExport;
</script>
</head>
<body>
${this.getScripts(nonce, scripts)}`;
if (options.fullList) {
diagram += `
<div class="header">
<span class="rrd-color"><span class="graph-initial">Ⓡ</span>rd </span>All rules
<span class="action-box">
Save to HTML<a onClick="graphExport.exportToHTML('rrd', '${baseName}');">
<span class="rrd-save-image" />
</a>
</span>
</div>
<div id="container">`;
const symbols = this.backend.listTopLevelSymbols(fileName, false);
for (const symbol of symbols) {
if (symbol.kind === SymbolKind.LexerRule
|| symbol.kind === SymbolKind.ParserRule
|| symbol.kind === SymbolKind.FragmentLexerToken) {
const script = this.backend.getRRDScript(fileName, symbol.name);
diagram += `<h3 class="${symbol.name}-class">${symbol.name}</h3>
<script nonce="${nonce}">${script}</script>`;
}
}
diagram += "</div>";
} else {
diagram += `
<div class="header">
<span class="rrd-color">
<span class="graph-initial">Ⓡ</span>ule
</span>
${this.currentRule} <span class="rule-index">(rule index: ${this.currentRuleIndex})
</span>
<span class="action-box">
Save to SVG
<a onClick="graphExport.exportToSVG('rrd', '${this.currentRule}');">
<span class="rrd-save-image" />
</a>
</span>
</div>
<div id="container">
<script nonce="${nonce}" >${this.backend.getRRDScript(fileName, this.currentRule)}</script>
</div>`;
}
diagram += `
<script nonce="${nonce}" type="module">
import { GraphExport } from "${exportScriptPath}";
graphExport = new GraphExport();
</script>
</body></html>`;
return diagram;
}
As you can see I set a base href in the code, which helps with relative imports. The entire implementation is split into two parts. One is in the tag where the graphExport variable is declared, to allow it to be used by event handling code. This variable is then initialized in the tag, where the GraphExport class is imported.
so I was following this https://webpack.js.org/guides/author-libraries/ from the web pack documentation, my file is just slightly different. No matter how I try to do this, I get various problems.
in my src/index.js file I have a simple function for example.
const Dinero = require('dinero.js')
export function calculateGrossRev(hudmonthly){
// Calculates the yearly gross rev
const hudonebr = Dinero({amount: hudmonthly, currency: 'USD'})
.multiply(12);
return hudonebr.getAmount();
}
In my package.json file I have.
{
"name": "library",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"dinero": "^1.0.1",
"lodash": "^4.17.21",
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"dinero.js": "^1.9.0"
}
}
In my webpack config file I have.
const path = require('path');
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'dinero-calc.js',
library: {
name: "dineroNumbers",
type: "umd",
},
},
};
when I run "npm run build", I get the big bundled file, all looks well to me.
I then have an index.html file I run with Live Server in VScode.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TEST</title>
</head>
<h1>TEST</h1>
<script src="./dinero-calc.js"></script>
<script>
console.log(window.dineroNumbers.calculateGrossRev(8200));
</script>
<body>
</body>
</html>
With the simple example from the docs I'm able to get the expected output from their expected function printed to the console, however with my imported function it says
index.html:11 Uncaught TypeError: Cannot read property 'calculateGrossRev' of undefined at index.html:11
I think a problem is in the script source url <script src="./dinero-calc.js"></script>. Please, check paths of index.html and dinero-calc.js.
According to your script tag, they should be in the same directory under dist folder.
[Upd]
First, install dinero.js which used in src/index.js.
npm i dinero.js
Also, dinero should be placed in dependencies, not devDependencies, because dinero.js used in your code.
Second, call function like this
Dinero.default({
amount: hudmonthly,
currency: "USD",
}).multiply(12);
I get the following error: "TypeError: glm__WEBPACK_IMPORTED_MODULE_0___default.a.vec3 is not a function" using webpack importing any npm package (the example is 'glm') on firefox.
main.js is:
import glm from "glm";
alert(glm.vec3(1, 2, 3));
package.json are:
{
"name": "webpacktest",
"version": "1.0.0",
"private": "true",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"glm": "^1.0.0"
},
"devDependencies": {
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0"
}
}
index.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
<script src="dist/main.js" charset="utf-8"></script>
</html>
the file system is
webpackettest
dist
main.js
node_modules
...
src
index.js
index.html
package.json
package-lock.json
I followed the "getting started" guide of webpack with no success.
edited: main.js file, it does not have an "a" attribute, still get the exact same error
Fixed it, the problem was that I was using the wrong npm package. It should be "glm-js" not "glm".
By the way the correct way to import a package from webpack is:
import * as glm from 'glm-js';
instead of:
import glm from 'glm-js';
I am having difficulty configuring webpack4 to properly bundle shared dependencies.
I have two pages in my application (Page1 and Page2). Both require bootstrap, jquery as well as a custom JavaScript app called core.
Page 2 requires the same but also a custom JavaScript application called my-app and also lodash.
Since my core app will be included in all pages, I want to have jquery and bootstrap in the same bundle.
Since lodash is only required for pages running my-app, I want to include that dependency in the my-app bundle.
So I setup my app like this:
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
'core': './src/core/index.js',
'my-app': './src/my-app/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
alias: {
jquery: 'jquery/src/jquery',
}
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
],
mode: 'development',
}
page1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
</head>
<body>
<h1>Page1</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
page2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
<script src="dist/my-app.bundle.js"></script>
</head>
<body>
<h1>Page2</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
(Full project: https://github.com/LondonAppDev/webpack-split-example)
When I run npx webpack, it creates core.bundle.js and my-app.bundle.js, however both of these include jquery.
Is it possible to put all "global" dependencies in core.bundle.js?
Just one thing to remember here, with webpack 4 you don't add vendor scripts as an entry to your webpack.config, just real entry scripts to your application.
WP will create an optimized bundle output for your app using the default settings.
You have to add vendor cache group to your config, in order to extract jQuery, Lodash, Bootstrap,Popper into a separate bundle:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
name: "vendor",
chunks: "all",
enforce: true
}
}
}
},
I try to make a simple using of - grunt-include-source but I don't success -
My Gruntfile is -
module.exports = function (grunt) {
grunt.initConfig({
includeSource: {
options: {
basePath: 'app/file1.js'
},
myTarget: {
files: {
'dist/index.html': 'app/index.html'
}
}
}
});
grunt.loadNpmTasks('grunt-include-source');
grunt.registerTask('serve', function (target) {
grunt.task.run('includeSource');
});
}
The index.html is -
<html>
<head>
</head>
<body>
<!-- include: "type": "js", "files": "scripts/*.js" -->
</body>
</html>
My folder hierarchy is -
Gruntfile.js
app >
file1.js
index.html
dist >
index.html
I run grunt serve and get in the dist>index.html folder the output -
<html>
<head>
</head>
<body>
</body>
</html>
Without the expected - <script src="scripts/file1.js"></script>
I kept to follow as in the documentation and in this question ,
Why I don't get the expected output ?
You have two problems with your code, first in gruntfile you are specifying a wrong path, second on your html you are specifying a wrong source.
Let's go by parts, on your gruntfile you have this:
...
includeSource: {
options: {
basePath: 'app/file1.js'
},
...
The basepath option on the docs says:
Type: String or Array[String] Default value: ''
The base path to use when expanding files. Can be an array to support
expanding files from multiple paths.
What this allows us to do is including one or more paths as our base path for our scripts. So let's change our basePath to basePath: 'app', our Gruntfile.js will look like this:
module.exports = function (grunt) {
grunt.initConfig({
includeSource: {
options: {
basePath: 'app'
},
myTarget: {
files: {
'dist/index.html': 'app/index.html'
}
}
}
});
grunt.loadNpmTasks('grunt-include-source');
grunt.registerTask('serve', function (target) {
grunt.task.run('includeSource');
});
};
Now if we run grunt serve it won't work, why? Well because on your index.html you have this:
<!-- include: "type": "js", "files": "scripts/*.js" -->
Which means, insert script tags for all the javascript files, inside the scripts folder, but we don't have any scripts folder, so that's why your dist/index.html is empty. Let's change our index.html to this:
<!-- include: "type": "js", "files": "*.js" -->
Run grunt serve et voilà your index.html has changed to:
<html>
<head>
</head>
<body>
<script src="file1.js"></script>
</body>
</html>
Now you just have to copy file1.js from app/ to dist/