I want esbuild to automatically handle the duplicate imports in the file for me, and I also want to use its tree shaking capabilities to help me optimise my code and then output it to a file, I wrote the following code to try and do the above, but I could never get it right
export interface ConfigSource extends Partial<Options> {
code: string
name: string
loader: Loader
}
export interface Config {
sources: ConfigSource[]
options?: BuildOptions
}
function babelBuildWithBundle(main: ConfigSource, config: Config) {
const buildModuleRuntime: Plugin = {
name: 'buildModuleRuntime',
setup(build) {
build.onResolve({ filter: /\.\// }, (args) => {
return { path: args.path, namespace: 'localModule' }
})
build.onLoad({ filter: /\.\//, namespace: 'localModule' }, (args) => {
const source = config.sources.find(
(source) =>
source.name.replace(/\..+$/, '') === args.path.replace(/^\.\//, '')
)
const content = source?.code || ''
return {
contents: content,
loader: source?.loader || 'js',
}
})
},
}
return build(
{
stdin: {
contents: main.code,
loader: main.loader,
sourcefile: main.name,
resolveDir: path.resolve('.'),
},
bundle: true,
write: false,
format: 'esm',
outdir: 'dist',
plugins: [buildModuleRuntime],
}
)
}
const foo = `
export const Foo = FC(() => {
return <div>gyron</div>
})
`
const app = `
import { Foo } from './foo'
function App(): any {
console.log(B)
return <Foo />
}
`
const bundle = await babelBuildWithBundle(
{
loader: 'tsx',
code: app,
name: 'app.tsx',
},
{
sources: [
{
loader: 'tsx',
code: foo,
name: 'foo.tsx',
},
],
}
)
Then the only answer I got in the final output was
console.log(bundle.outputFiles[0].text)
`
// localModule:. /foo
var Foo = FC(() => {
return /* #__PURE__ */ React.createElement("div", null, "gyron");
});
`
console.log(bundle.outputFiles[1])
`
undefined
`
I have just tried setting treeShaking to false and can pack the app into the product.
Related
I'm not very familiar with webpack.
My goal was to put all the assets inside my HTML in a specific folder.
For that, I set a new option under the rule that deals with the `type: assets/resource:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option setted
}
}
It does actually work. webpack creates the folder after compiling and brings those files inside it. The problem is that the HTML file compiled doesn't understand that the assets files are inside assets/ folder.
How can I fix it?
Here is my webpack.config.js
const path = require('path')
const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')
const stylesHandler = MiniCssExtractPlugin.loader
const base = {
entry: {
bundle: [
'./js/main.js',
'./css/style.scss'
]
},
output: {
path: path.resolve(__dirname, 'docs')
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
scriptLoading: 'module',
inject: 'body'
}),
new MiniCssExtractPlugin()
],
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option setted
}
},
{
test: /\.html$/i,
use: ['html-loader']
}
]
}
}
const dev = {
devServer: {
open: false,
host: 'localhost',
watchFiles: ['./index.html']
}
}
const prod = {
output: {
clean: true
}
}
module.exports = (env, args) => {
switch (args.mode) {
case 'development':
return merge(base, dev)
case 'production':
return merge(base, prod)
default:
throw new Error('No matching configuration was found!')
}
}
So I discover it by myself, and it was kinda obvious.
Well, when webpack compiles your index.html file, it won't understand if you just give a new path for your assets final destiny.
For example, like I did:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option set
}
}
In order this to work you need to specify the publicPath:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option set
publicPath: 'assets/'
}
}
You're telling webpack:
Put all the assets inside outputPath.
Hey HTML, when you look for the assets, please include the publicPath before looking for it.
I'm learning how to write code of service worker and stuck with the error "ReferenceError: document is not defined" in my app.js file. I'm using workbox library with InjectManifest mode. I think the problem in the webpack.config.js, because when I delete InjectManifest in webpack.config.js the error disappears.
My webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const {InjectManifest} = require('workbox-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.html$/i,
use: [
{
loader: 'html-loader',
},
],
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader',
],
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
],
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new InjectManifest({
swSrc: './src/js/service.worker.js',
swDest: 'service.worker.js',
}),
],
};
My service.worker.js file:
import { precacheAndRoute } from 'workbox-precaching/precacheAndRoute';
import { cinemaNews } from './cinemaNews';
import { url } from './app';
precacheAndRoute(self.__WB_MANIFEST);
const CACHE_NAME = 'v1';
const responseCache = new Response(JSON.stringify(cinemaNews));
self.addEventListener('install', (evt) => {
console.log('install')
evt.waitUntil((async () => {
console.log('install waitUntil')
const cache = await caches.open(CACHE_NAME);
await cache.put(url, responseCache);
await self.skipWaiting();
})());
});
self.addEventListener('activate', (evt) => {
console.log('activate')
evt.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', (evt) => {
console.log('sw fetch')
const requestUrl = new URL(evt.request.url);
if (!requestUrl.pathname.startsWith('/news')) return;
evt.respondWith((async () => {
console.log('respondWith')
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(evt.request);
return cachedResponse;
})());
evt.waitUntil((async () => {
console.log('waitUntil');
const response = await fetch(evt.request.url);
const client = await clients.get(evt.clientId);
let json = await response.json();
client.postMessage(json);
})());
});
This statement:
import { url } from './app';
appears to be triggering the issue, as there must be code inside of your app.js that is executed via that import, and which assumes that document will be defined. (It's not defined inside of the ServiceWorkerGlobalScope.)
Based on how you're using the export, I'm assuming that it's just a string constant containing a shared URL that you want to use from both your main web app and your service worker. Assuming that's the case, the easiest thing to do would be to refactor your modules that there's a constants.js (or some similar name) module that only exports your string constants, and doesn't try to run any code that references document. You can then import the constant from either your web app or the service worker without issue.
// constants.js
export const url = '/path/to/url';
// service-worker.js
import {url} from './constants';
// do something with url
// app.js
import {url} from './constants';
// do something with url
There is an existing API described in a Coludformation template. Now I want to document the API using Swagger. Is there a way to parse the Cloudformation template to create the swagger.yaml specification file? I would like to avoid writing the API a second time, if possible.
Note: I am aware that you can define your API using Swagger, then import the API configuration in your Cloudformation template. This is not what I need. The Cloudformation already exists and will not be changed. Hence, I need the opposite: a Swagger configuration file based on an existing Cloudformation template.
There is no way to convert the template to a swagger file that I know about. But if you are looking for a way to keep service-spec in one place only (template) and you have it deployed, you can take swagger or OAS file from the stage (so to do it you must have a stage as well) in two ways at least:
By Web console. Use Amazon API Gateway->
APIs->Your API->Stages>Your Stage -> Export tab. See the picture: exporting Swagger or OAS as a file by Web console
aws apigateway get-export ... Here is an example:
aws apigateway get-export --rest-api-id ${API_ID} --stage-name ${STAGE_NAME} --export-type swagger swagger.json
I just made this, it is not setup for perfect plug/play, but will give you an idea what you need to adjust to get it working (also need to make sure you CF template is setup so it has the needed info, on mine I had to add some missing requestParams I was missing, also use this site to test your results from this code to see it works with swagger):
const yaml = require('js-yaml');
const fs = require('fs');
// Get document, or throw exception on error
try {
// loads file from local
const inputStr = fs.readFileSync('../template.yaml', { encoding: 'UTF-8' });
// creating a schema to handle custom tags (cloud formation) which then js-yaml can handle when parsing
const CF_SCHEMA = yaml.DEFAULT_SCHEMA.extend([
new yaml.Type('!ImportValue', {
kind: 'scalar',
construct: function (data) {
return { 'Fn::ImportValue': data };
},
}),
new yaml.Type('!Ref', {
kind: 'scalar',
construct: function (data) {
return { Ref: data };
},
}),
new yaml.Type('!Equals', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::Equals': data };
},
}),
new yaml.Type('!Not', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::Not': data };
},
}),
new yaml.Type('!Sub', {
kind: 'scalar',
construct: function (data) {
return { 'Fn::Sub': data };
},
}),
new yaml.Type('!If', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::If': data };
},
}),
new yaml.Type('!Join', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::Join': data };
},
}),
new yaml.Type('!Select', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::Select': data };
},
}),
new yaml.Type('!FindInMap', {
kind: 'sequence',
construct: function (data) {
return { 'Fn::FindInMap': data };
},
}),
new yaml.Type('!GetAtt', {
kind: 'scalar',
construct: function (data) {
return { 'Fn::GetAtt': data };
},
}),
new yaml.Type('!GetAZs', {
kind: 'scalar',
construct: function (data) {
return { 'Fn::GetAZs': data };
},
}),
new yaml.Type('!Base64', {
kind: 'mapping',
construct: function (data) {
return { 'Fn::Base64': data };
},
}),
]);
const input = yaml.load(inputStr, { schema: CF_SCHEMA });
// now that we have our AWS yaml copied and formatted into an object, lets pluck what we need to match up with the swagger.yaml format
const rawResources = input.Resources;
let guts = [];
// if an object does not contain a properties.path object then we need to remove it as a possible api to map for swagger
for (let i in rawResources) {
if (rawResources[i].Properties.Events) {
for (let key in rawResources[i].Properties.Events) {
// console.log(i, rawResources[i]);
if (rawResources[i].Properties.Events[key].Properties.Path) {
let tempResource = rawResources[i].Properties.Events[key].Properties;
tempResource.Name = key;
guts.push(tempResource);
}
}
}
} // console.log(guts);
const defaultResponses = {
'200': {
description: 'successful operation',
},
'400': {
description: 'Invalid ID supplied',
},
};
const formattedGuts = guts.map(function (x) {
if (x.RequestParameters) {
if (
Object.keys(x.RequestParameters[0])[0].includes('path') &&
x.RequestParameters.length > 1
) {
return {
[x.Path]: {
[x.Method]: {
tags: [x.RestApiId.Ref],
summary: x.Name,
parameters: [
{
name: Object.keys(x.RequestParameters[0])[0].split('method.request.path.')[1],
in: 'path',
type: 'string',
required: Object.values(x.RequestParameters[0])[0].Required,
},
{
name: Object.keys(x.RequestParameters[1])[0].split('method.request.path.')[1],
in: 'path',
type: 'string',
required: Object.values(x.RequestParameters[1])[0].Required,
},
],
responses: defaultResponses,
},
},
};
} else if (Object.keys(x.RequestParameters[0])[0].includes('path')) {
return {
[x.Path]: {
[x.Method]: {
tags: [x.RestApiId.Ref],
summary: x.Name,
parameters: [
{
name: Object.keys(x.RequestParameters[0])[0].split('method.request.path.')[1],
in: 'path',
type: 'string',
required: Object.values(x.RequestParameters[0])[0].Required,
},
],
responses: defaultResponses,
},
},
};
} else if (Object.keys(x.RequestParameters[0])[0].includes('querystring')) {
return {
[x.Path]: {
[x.Method]: {
tags: [x.RestApiId.Ref],
summary: x.Name,
parameters: [
{
name: Object.keys(x.RequestParameters[0])[0].split(
'method.request.querystring.'
)[1],
in: 'query',
type: 'string',
required: Object.values(x.RequestParameters[0])[0].Required,
},
],
responses: defaultResponses,
},
},
};
}
}
return {
[x.Path]: {
[x.Method]: {
tags: [x.RestApiId.Ref],
summary: x.Name,
responses: defaultResponses,
},
},
};
});
const swaggerYaml = yaml.dump(
{
swagger: '2.0',
info: {
description: '',
version: '1.0.0',
title: '',
},
paths: Object.assign({}, ...formattedGuts),
},
{ noRefs: true }
); // need to keep noRefs as true, otherwise you will see "*ref_0" instead of the response obj
// console.log(swaggerYaml);
fs.writeFile('../swagger.yaml', swaggerYaml, 'utf8', function (err) {
if (err) return console.log(err);
});
} catch (e) {
console.log(e);
console.log('error above');
}
I have a basic react functional component and I bind an array in which there are strings to be localized. Is there any other way to do it? I am trying as below and it says "Invalid Hook Call"
import { useIntl } from "react-intl";
const NavBasicExample: React.FunctionComponent = () => {
return (
<Nav
groups={navLinkGroups}
/>
</div>
);
};
const navLinkGroups: INavLinkGroup[] = [
{
name: getFormattedString(Strings.details),//This fails and says invalidHookCall
links: [{ name: Strings.appDetails, url: "" }]
},
{
name: Strings.capabilities,
links: [
{ name: Strings.tabs},
{ name: Strings.bots}
]
}
];
const getFormattedString = (inputString: string) => {
const intl = useIntl(); //This fails.
return intl.formatMessage({ id: "details", defaultMessage: "Login });
};
The problem is that you are calling a Hook from a non-react function. You are not allowed to do that. Try moving the "navLinkGroups" into the "NavBasicExample" and it should work
import { useIntl } from "react-intl";
const NavBasicExample: React.FunctionComponent = () => {
const intl = useIntl();
const navLinkGroups: INavLinkGroup[] = [
{
name: getFormattedString(Strings.details),
links: [{ name: Strings.appDetails, url: "" }]
},
{
name: Strings.capabilities,
links: [
{ name: Strings.tabs},
{ name: Strings.bots}
]
}
];
const getFormattedString = (inputString: string) => {
return intl.formatMessage({ id: "details", defaultMessage: "Login" });
};
return (
<Nav
groups={navLinkGroups}
/>
</div>
);
};
I am getting the following error - 'Module build failed: Error: Final loader didn't return a Buffer or String' when trying to compile JSX components for server-side rendering using Webpack 2 and ReactJS.NET. I have followed the examples as described here as well as in this example.
My folder structure is similar to the Github example, I have one React component, an index.js file and a server.js file. Here are the contents:
/Content/components/TestComponent.jsx
var React = require('react');
class Test extends React.Component {
getInitialState() {
return { data: this.props.greeting };
}
render() {
return (
<div>
<h1>{this.props.greeting}</h1>
</div>);
}
}
module.exports = Test;
/Content/components/index.js
module.exports = {
Test: require('./TestComponent.jsx')
}
/Content/server.js
var Components = require('expose?Components!./components');
And here is my full webpack.config.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './Content/server',
output: {
path: __dirname + '/resources/dev/scripts/server',
filename: 'test.js'
},
module: {
rules: [
{
test: /\.js$|.jsx$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['env', 'react'],
}
}
{
test: require.resolve('react'),
loader: 'expose?React'
}
]
},
resolve: {
extensions: ['.js', '.jsx']
},
externals: {
react: 'React'
}
}
I have created an example project here with the same problem.
The solution was to do away with the /Content/server.js file, and instead use the expose-loader in webpack. Here is the updated webpack.config.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './Content/components/index',
output: {
path: __dirname + '/resources/dev/scripts/server',
filename: 'test.js'
},
module: {
rules: [
{
test: /\.js$|.jsx$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['env', 'react'],
}
},
{
test: require.resolve('./Content/components/index'),
use: [{
loader: 'expose-loader',
options: 'Components'
}]
},
{
test: require.resolve('react'),
use: [{
loader: 'expose-loader',
options: 'React'
}]
}
]
},
resolve: {
extensions: ['.js', '.jsx']
}
}