How to change the template file(s) for an existing nx schematic - reactjs

I started to use https://nx.dev/ recently to reorganize an existing monorepo with multiple react frontends and redux state management.
nx provides the ability to create new redux slices out of the box using the #nrwl/react:redux schematic like: nx g #nrwl/react:redux <sliceName>. This is great! However, the template that is being used to create new files does not fit my needs (for example I do not use redux-thunk...) and I would like to use my own template.
I created a new custom schematic with nx g workspace-schematic redux-module and adjusted it to extend #nrwl/react:redux like:
import { chain, externalSchematic, Rule } from '#angular-devkit/schematics';
export default function(schema: any): Rule {
return chain([
externalSchematic('#nrwl/react', 'redux', {
name: schema.name
})
]);
}
Can anyone tell me how to proceed from here to make the custom schematic use my own template files?
Thanks!

You can have the template files inside the directory, load them and then copy to the target destination.e.g.
Dir structure:
Project_root
|- tools
|- schematics
|- your_schematics
|- index.ts
|- templates
|- __name#dasherize__.custom.ts
Content:
/* __name#dasherize__.custom.ts */
export class <%= classify(name) %>Custom {
constructor () {}
}
/* index.ts */
import { chain, externalSchematic, Rule, url, apply, move, mergeWith, MergeStrategy, template, SchematicContext } from '#angular-devkit/schematics';
import { dasherize, classify } from '#angular-devkit/core/src/utils/strings';
import { normalize, strings } from '#angular-devkit/core';
export default function(schema: any): Rule {
const libFileName = dasherize(schema.name);
const projectDirectory = schema.directory
? normalize(schema.directory + '/' + libFileName)
: libFileName;
const projectRoot = normalize('libs/' + projectDirectory);
return chain([
externalSchematic('#nrwl/workspace', 'lib', schema),
addCustomFileToLib(schema, projectRoot)
]);
}
function addCustomFileToLib(schema: any, projectRoot: string): Rule {
const templateFiles = url("./templates");
const newTree = apply(templateFiles, [
move(projectRoot),
template({
...strings,
...schema // pass the objects containing the properties & functions to be used in template file
})
]);
return mergeWith(newTree, MergeStrategy.Default);
}
Note: Name of template file(__name#dasherize__.custom.ts) is an expression which will be compiled/changed according to the name arg passed from cli while executing the schematics.

Related

Importing functions from other files not working (React)

Import:
import { get, set, faviconChange } from '/js/title.js';
title.js
var geta = a => localStorage.getItem(a)
var seta = (a, b) => localStorage.setItem(a, b)
export function get(a) {
localStorage.getItem(a);
}
export function set(a,b) {
localStorage.setItem(a,b);
}
document.title = get('title') || 'Anonymous'
var link = document.createElement('link');
link.rel = 'icon';
document.getElementsByTagName('head')[0].appendChild(link);
export function faviconChange(value) {
set('link', value)
link.href = get('link') || '/favicon.ico';
console.log("working " + link.href)
}
link.href = get('link') || '/favicon.ico';
Error:
./pages/index.js:6:0
Module not found: Can't resolve '/js/title.js'
4 | import Particles from 'react-tsparticles';
5 | import { loadFull } from "tsparticles";
> 6 | import { get, set, faviconChange } from '/js/title.js';
7 |
8 |
9 | export default function Home() {
https://nextjs.org/docs/messages/module-not-found
Whenever I try and import these functions, I get the above error and I've looked it up, and everyone seems to do it this way, yet I get an error. Am I missing something/putting these imports in the wrong file?
Try to import from './js/title.js';
When importing from another file, you must go up in the files hierarchy with a dot './'
3 suggestions are here.
1. adding export in your importing file
export default YourFunctionName;
2. As #SaF mentioned, make sure the path is correct
How?
If you are using linux/unix/mac use the command tree on the parent folder to get the exact path (if windows, you can install tree)
try using ../../../folder_x to go to folder_x which is 3 folders behind and ./ to get current folder
try printing your js file's absolute path using fs module https://www.npmjs.com/package/fs-js (comment the import line and try this)
3. Change jsx file extention to normal js
and add below code in webpack.config.js to resolve jsx as js
module.exports = {
//...
resolve: {
extensions: ['.js', '.jsx']
}
};

How can I emit types with custom prefixes and custom casing when using Apollo Codegen?

We are using https://www.apollographql.com/docs/react/development-testing/developer-tooling/#apollo-codegen.
I am wondering if it is possible to generate types/interfaces for TypeScript with a custom prefix & casing? For example, let's say we have a .graphql file:
query homePage {
user {
id
}
}
When we run the script to generate type defs we end up with this:
export interface homePage {
user: homePage_user | null;
}
However, what we'd actually like is:
export interface THomePage {
user: THomePage_user | null;
}
Is this possible?
Our current script to generate the code is: "apollo client:codegen --includes=**/*.ts.graphql --endpoint=http://localhost:3100 --tsFileExtension=d.ts --target=typescript --globalTypesFile=src/__generated__/Types.d.ts --passthroughCustomScalars --customScalarsPrefix=GQL."

react native (expo) load markdown files

I'm having some troubles loading markdown files (.md) into my react native (non-detached expo project).
Found this awesome package that allows me to render it. But can't figure out how to load the local .md file as a string.
import react from 'react';
import {PureComponent} from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class Page extends PureComponent {
static propTypes = {};
static defaultProps = {};
render() {
return (
<Markdown>{copy}</Markdown>
);
}
}
BTW: I tried googling, but can't get the suggestions to work
https://forums.expo.io/t/loading-non-media-assets-markdown/522/2?u=norfeldtconsulting
I tried the suggested answers for reactjs on SO, but the problem seems to be that it only accepts .js and .json files
Thanks to #Filipe's response, I got some guidance and got a working example that will fit your needs.
In my case, I had a .md file on the assets/markdown/ folder, the file is called test-1.md
The trick is to get a local url for the file, and then use the fetch API to get its content as a string.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: copy
}
}
componentDidMount() {
this.fetchLocalFile();
}
fetchLocalFile = async () => {
let file = Expo.Asset.fromModule(require("./assets/markdown/test-1.md"))
await file.downloadAsync() // Optional, saves file into cache
file = await fetch(file.uri)
file = await file.text()
this.setState({copy: file});
}
render() {
return (
<Markdown>{this.state.copy}</Markdown>
);
}
}
EDIT: In order to get get rid of the error
Unable to resolve "./assets/markdown/test-1.md" from "App.js"
you would need to add the packagerOpts part of #Filipe's snippet into your app.json file.
app.json
{
"expo": {
...
"assetBundlePatterns": [
"**/*"
],
"packagerOpts": {
"assetExts": ["md"]
},
...
}
}
EDIT 2:
Answering to #Norfeldt's comment:
Although I use react-native init when working on my own projects, and I'm therefore not very familiar with Expo, I got this Expo Snack that might have some answers for you: https://snack.expo.io/Hk8Ghxoqm.
It won't work on the expo snack because of the issues reading non-JSON files, but you can test it locally if you wish.
Using file.downloadAsync() will prevent the app making XHR calls to a server where your file is hosted within that app session (as long as the user does not close and re-open the app).
If you change the file or modify the file (simulated with a call to Expo.FileSystem.writeAsStringAsync()), it should display the updated as long as your component re-renders and re-downloads the file.
This will happen every time your app is closed and re-open, as the file.localUri is not persisted per sessions as far as I'm concerned, so your app will always call file.downloadAsync() at least once every time it's opened. So you should have no problems displaying an updated file.
I also took some time to test the speed of using fetch versus using Expo.FileSystem.readAsStringAsync(), and they were on average the same. Often times Expo.FileSystem.readAsStringAsync was ~200 ms faster, but it 's not a deal breaker in my opinion.
I created three different methods for fetching the same file.
export default class MarkdownRenderer extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: ""
}
}
componentDidMount() {
this.fetch()
}
fetch = () => {
if (this.state.copy) {
// Clear current state, then refetch data
this.setState({copy: ""}, this.fetch)
return;
}
let asset = Expo.Asset.fromModule(md)
const id = Math.floor(Math.random() * 100) % 40;
console.log(`[${id}] Started fetching data`, asset.localUri)
let start = new Date(), end;
const save = (res) => {
this.setState({copy: res})
let end = new Date();
console.info(`[${id}] Completed fetching data in ${(end - start) / 1000} seconds`)
}
// Using Expo.FileSystem.readAsStringAsync.
// Makes it a single asynchronous call, but must always use localUri
// Therefore, downloadAsync is required
let method1 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
})
} else {
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
}
}
// Use fetch ensuring the usage of a localUri
let method2 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
fetch(asset.localUri).then(res => res.text()).then(save)
})
} else {
fetch(asset.localUri).then(res => res.text()).then(save)
}
}
// Use fetch but using `asset.uri` (not the local file)
let method3 = () => {
fetch(asset.uri).then(res => res.text()).then(save)
}
// method1()
// method2()
method3()
}
changeText = () => {
let asset = Expo.Asset.fromModule(md)
Expo.FileSystem.writeAsStringAsync(asset.localUri, "Hello World");
}
render() {
return (
<ScrollView style={{maxHeight: "90%"}}>
<Button onPress={this.fetch} title="Refetch"/>
<Button onPress={this.changeText} title="Change Text"/>
<Markdown>{this.state.copy}</Markdown>
</ScrollView>
);
}
}
Just alternate between the three to see the difference in the logs.
From what I know, this cannot be done within expo. I use react-native and run it on my mobile for development.
react-native use Metro as the default bundler, which also suffers from similar problems. You have to use haul bundler instead.
npm install --save-dev haul
npx haul init
npx haul start --platform android
In a seperate terminal run react-native run-android. This would use haul instead of metro to bundle the files.
To add the markdown file, install raw-loader and edit the haul.config.js file. raw-loader imports any file as a string.
Customise your haul.config.js to look something like this:
import { createWebpackConfig } from "haul";
export default {
webpack: env => {
const config = createWebpackConfig({
entry: './index.js',
})(env);
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader'
})
return config;
}
};
Now you can import the markdown file by using const example = require('./example.md')
Haul supports webpack configuration so you can add any custom babel transform you want.
I don't know exactly where the problem lies, but I added html files to the project, and I'd imagine it would be very similar.
Inside your app.json, try adding these fields:
"assetBundlePatterns": [
"assets/**",
],
"packagerOpts": {
"assetExts": ["md"]
},
The packagerOpts makes it so the standalone will bundle the .md files. I'd imagine you already have an assets folder, but just in case you don't, you will need one.
Then, on AppLoading, loading the assets with Asset.loadAsync might not be needed, but it's a good idea to rule out. Check out the documentation on how to use it.
When importing the file, there are three ways you might want to do so, that change depending on the environment. I'll copy this excerpt from my Medium article:
In the simulator, you can access any file in the project. Thus, source={require(./pathToFile.html)} works. However, when you build a standalone, it doesn’t work quite in the same way. I mean, at least for android it doesn’t. The android webView doesn’t recognise asset:/// uris for some reason. You have to get the file:/// path. Thankfully, that is very easy. The assets are bundled inside file:///android_asset (Careful, don’t write assets), and Expo.Asset.fromModule(require(‘./pathToFile.html')).localUri returns asset:///nameOfFile.html. But that’s not all. For the first few times, this uri will be correct. However, after a while, it changes into another file scheme, and can’t be accessed in the same way. Instead, you’ll have to access the localUri directly. Thus, the complete solution is:
/* Outside of return */
const { localUri } = Expo.Asset.fromModule(require('./pathToFile.html'));
/* On the webView */
source={
Platform.OS === ‘android’
? {
uri: localUri.includes('ExponentAsset')
? localUri
: ‘file:///android_asset/’ + localUri.substr(9),
}
: require(‘./pathToFile.html’)
}
(A constant part of the uri is ExponentAsset, that’s why I chose to check if that was part of it)
That should probably solve your problem. If it doesn't, comment what's going wrong and I'll try to help you further. Cheers!
If you want to load .md file with react-native cli (without expo). I've got a solution for you)
Add https://github.com/feats/babel-plugin-inline-import to your project
Add config .babelrc file with code inside:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
[
"inline-import",
{
"extensions": [".md", ".txt"]
}
],
[
"module-resolver",
{
"root": ["./src"],
"alias": {}
}
]
]
}
Add to your metro.config.js such code
const metroDefault = require('metro-config/src/defaults/defaults.js');
...
resolver: {
sourceExts: metroDefault.sourceExts.concat(['md', 'txt']),
}
....
Reload your app

How to use "webpack.DefinePlugin" with React Gatsby and React-Bodymoving?

I am pretty new to React but I want to set
BODYMOVIN_EXPRESSION_SUPPORT in Webpack's Define plugin with Gatsby v1.
I followed the links below but I don't get what exactly I suppose to do...
https://github.com/QubitProducts/react-bodymovin
https://www.gatsbyjs.org/docs/environment-variables/
I made the file named .env.development and put it to src folder. the content in this file is below.
plugins: ([
new webpack.DefinePlugin({
BODYMOVIN_EXPRESSION_SUPPORT: true
})
])
The folder structures is
root--
|
|- public //where the build goes
|
|- src -- //where I develop site
|-components
|-data
|-pages
|-style
|-.env.development
What I noticed is there is a line said
/*global BODYMOVIN_EXPRESSION_SUPPORT*/
in bodymovin library and I think I just need to change that. I could modify in library directly maybe but I don't think that a best way to get around this problem. Does someone know how to set this up right?
Thanks in advance!
EDIT 2019-09-02
To use environment variables from .env files I recommend using dotenv because it's so simple. Here's an example that creates an object of all the variables in the .env file and makes them accessible on the client side (i.e in React) through DefinePlugin.
// gatsby-node.js
var dotenv = require('dotenv');
const env = dotenv.config().parsed;
// Create an object of all the variables in .env file
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) => {
actions.setWebpackConfig({
plugins: [
// Add the environment variables to webpack.DefinePlugin with define().
plugins.define(envKeys)
]
});
};
Here's an example of how I get the application name and version from package.json and using it in my service worker, I'm using Gatsby V2 though. Having the version in the service worker makes caching easier to handle. As you wrote, DefinePlugin is the way to go but it's a bit different when we use it in Gatsby.
We need to import the package.json file and add our custom webpack configuration in gatsby-node.js, with plugins.define() we tell webpack to use DefinePlugin:
const packageJson = require('./package');
exports.onCreateWebpackConfig = ({
plugins,
actions,
}) => {
actions.setWebpackConfig({
plugins: [
plugins.define({
__NAME__: JSON.stringify(packageJson.name),
__VERSION__: JSON.stringify(packageJson.version),
}),
],
})
}
The two defined variables __NAME__ and __VERSION__ are now accessible in my service worker sw.js:
self.addEventListener('install', function (e) {
// eslint-disable-next-line
console.log(__NAME__, __VERSION__);
e.waitUntil(
caches.open(__NAME__ + __VERSION__).then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
Gatsby Reference: https://www.gatsbyjs.org/docs/add-custom-webpack-config/

Handlebars custom helper - migrating to Webpack

We have an app based on backbone, marionette and handlebars, without import/export or require methods, managed with grunt and we are trying to migrate to webpack.
I am having an issue with a custom helper for handlebars.
The code of our helper :
'use strict';
function I18n() {
this.constructor(arguments);
}
I18n.prototype = {
constructor: function () {
...some stuff
}
get: function () {
...some stuff
}
...some other functions
}
ourNameSpace.I18n = new I18n();
And it's included with this function in a file to load it globally :
Handlebars.registerHelper('i18n', _.bind(ourNameSpace.I18n.get, ourNameSpace.I18n));
Then we are using it in the template like this :
{{i18n "LblEmail"}}
I tried to use handlebars-loader and I added this query object into webpack.config to add it to the bundle :
{
test: /\.hbs$/,
use: {
loader: 'handlebars-loader',
query: {
helperDirs: [
path.resolve(__dirname, 'public/assets/js/common/i18n/')
]
}
}
}
Webpack add our helper code in the bundle, but when it's supposed to be called in the template I have this error :
Uncaught TypeError: __default(...).call is not a function
Webpack generated code of the bundle where is the call :
...
+ alias2(__default(__webpack_require__(2)).call(alias1,"LblEmail",{"name":"i18n","hash":{},"data":data}))
...
In a second time I also tried to add an export in the helper, even though we don't use the import/export method (yet) in our app. Adding this at the end of helper file :
export default I18n
That fix the error but the helper doesn't seem to work because all texts on the page are empty (instead of displaying i18n translation or keys)
Does someone did the same kind of migration with handlebars custom helper or would know how I can refactor that so Webpack can handle it properly and the bundle can execute it correctly ?
So after few months I will reply to my own question, I managed to fix our problem like this :
I rewrite our old legacy helper (with custom functions) by creating more modern ones (three, for our three functions that was in legacy helper) relying on I18nJS:
import I18nJs from 'i18n-js';
const I18n = key => I18nJs.t(key);
export default I18n;
It is loaded by webpack with handlebars loaders like this :
{
test: /\.hbs$/,
use: {
loader: 'handlebars-loader?runtime=handlebars/runtime',
query: {
helperDirs: [path.resolve(__dirname, 'src/js/common/i18n/helper')],
inlineRequires: '/images/',
precompileOptions: {
knownHelpersOnly: false,
},
},
},
}
And in our template we did not have to change anything to use it :
<label>{{i18n "LblEmail"}}</label>
To use localisation on javascript files however we had to make some changes :
I created a "helper" (not handlebar helper) implementing same logic than handlebars helper :
import I18nJs from 'i18n-js';
const I18n = {
get(key) {
return I18nJs.t(key);
},
... some other functions
};
export default I18n;
We import this file and use its function as usual in modern stacks :
import I18n from '../common/i18n/I18nSt';
...
console.log(I18n.get('PasswordMissing'));
So we had to do minor refactor when we call our translations function in our js files, It was like this before :
console.log(OurNamespace.I18n.get('PasswordMissing'));

Resources