Open FredrikSigvartsen opened 5 years ago
Sorry, this is my fault. I published to npm but didn't actually test it in React. On a React site, we actually custom made a new component and wrapped it in the basic React stuff and then included the js notation below that (see attached).
@traviskochel / @tinymachine, is a proper component in order of is there an easy way to get it to work as is?
I could be way off but I think the issue might be the import statement handling Textblock as if it were being exported as an ES6 module, when it's not (unlike in your special ReactTextblock component, @theorosendorf).
@FredrikSigvartsen, does the following change fix the issue?
- import Textblock from "textblock";
+ import "textblock";
I think this will add Textblock
to the global namespace, as @traviskochel warns about here. I'm not sure how to allow the script to both be importable and be run as a vanilla script as it does now...
@tinymachine , this solves the current problem. I just want to leave the solution for others to see:
Expected behaviour in App.js:
import React, { Component } from "react";
import "./App.scss";
import "textblock";
class App extends Component {
componentDidMount() {
window.Textblock([
{
target: "h3",
minWidth: 400,
maxWidth: 800,
minWidthFontSize: 18,
maxWidthFontSize: 24,
minWidthLineHeight: 1.33,
maxWidthLineHeight: 1.2,
units: "px"
}
]);
}
render() {
return
<div className="App">
<h3>This title scales perfectly based on Textblock</h3>
</div>
);
}
}
export default App;
But I guess the expected behaviour is still my last post, using ES6 Modules. But the solution above will definitely work for now.
Anyone know of a good way to satisfy both scenarios in one codebase?
I don't know of a way to satisfy both scenarios, but I think the following code from the marked
repo might be relevant?
The script opens with:
;(function(root) {
'use strict';
And closes with:
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
define(function() { return marked; });
} else {
root.marked = marked;
}
})(this || (typeof window !== 'undefined' ? window : global));
I haven't had a chance to make sense of this yet; just posting in case it's helpful...
I worked up a proof-of-concept for a single file that works as an imported module or as a script loaded in index.html. Here's a sample React project on CodeSandbox that imports the script as a module using the syntax import Textblock from "./textblock-umd-test";
. And here's a vanilla HMTL project on CodePen that links to a copy of the same file using a <script>
tag.
(Note: these examples use parameter names proposed in PR #36.)
This is a bit over my head, but I used grunt-umd
to transform the source code into a UMD (universal module definition), which (I think) allows the script to be used as a CommonJS/AMD module (but not a more modern ES6 module) as well as a standard script. The grunt task works by wrapping UMD boilerplate code around the script (which increases the script size just a bit).
The uglify task that mangles function names and properties (also introduced in PR #36) is currently breaking the script's ability to be imported as a module. This can be mitigated by turning off mangling, but the minified script size will of course increase.
I'm not sure this is the most up-to-date approach, though. A better solution (but one that might be overkill) might be to leave the basic script alone, specified as pkg.main
as it is now, and then also compile it as an ES6 module (ESM), maybe using Rollup.js, and point to that version of the script in pkg.module
. (See the Rollup explanation of pkg.module
— essentially, Webpack and other bundlers look at the module
property first when resolving import
statements.)
Any input welcome...
@tinymachine – This sounds amazing but might be slightly over my head at the moment. Would there be any sense in running a poll to find what people want most or do we already know what that is? Is it plain JS and React, or the more specific ES6 designations?
Would there be any sense in running a poll to find what people want most or do we already know what that is? Is it plain JS and React, or the more specific ES6 designations?
@theorosendorf I'm out of my depth here, too — perhaps @FredrikSigvartsen or someone else could weigh in?
I think a React importable component would be great, but I think some more changes should be made to suit the React paradigm. Perhaps the core functionality could be extracted into a shared file, and then a vanilla version and a react version can import the core. You could then add as many other flavours as you like when react is no longer the hottest thing in the world.
Steps to take to split into vanilla/react versions
@billymoon Thanks for your detailed suggestions; I think you're exactly right that this is how Textblock should evolve. (Well, except in your point 5 above, you mention "browser versions of both vanilla and react". You mean compile a vanilla script for the browser and a separate module for React, no?)
I'm not familiar enough with React to tackle this myself. I know it's a decent chunk of work, but any chance you'd be willing to submit a PR? (This would be tremendously educational for me!) Cheers!
Hey guys, Textblock should now be available for import as an ES6 module. I’m sure it can be improved but works for now with a conditional statement testing if module.export
can be used and adds it if so.
I was confident enough to release it but please let me know if you see any bugs. Thanks!
Anyone here know about the React lifecycle and how we might adapt Textblock to work when loaded as a component and then again as a function when the DOM changes?
Of course @billymoon's comment about separating the concerns is the ultimate goal. I am testing a hacked version of Textblock in React. It works when loading a component but not subsequently. Is it “component / Will / Did / etc. / mount” that should be used?
@theorosendorf I'm planning on starting my first React project soon, so can't offer guidance now but hopefully in a few months! From what little I've read I think the relatively new useEffect
hook for function components may be relevant here, too, in addition to componentDidMount
in class components, but I barely know what I'm talking about!
Howdy folks,
I realize this thread is a bit old, but I've been using Textblock in a React site for a few years and thought I'd share a few things for anyone else that's looking.
First, I just submitted PR #74 that addresses some things for use in React (specifically, returning cancel handles to prevent memory leaks).
We obviously don't want a peer dependency on React in the core lib, so I haven't added any React specific code here. @theorosendorf, I propose we set up a textblock-react repo and NPM package for this purpose.
Anyhow, if you're using React strictly CSR (or are using client components, e.g. with the "use client"
pragma, useLayoutEffect
works well. There's a tradeoff compared to useEffect
in that useLayoutEffect
blocks rendering while the screen is repainted. This delays first paint, but it removes some of the potential layout shift associated with useEffect
. YMMV. If your components are server rendered, useLayoutEffect
won't work. In that case, useEffect
is your best (only) option.
Here's what it all looks like (in Typescript, vanilla JS works as well). Note that this code does depend on the changes in PR 74.
Create a React hook, like so:
// useTextblock.ts
import { Textblock, TextblockOptions, TextblockTarget } from "textblock";
import { useLayoutEffect } from "react";
export const useTextblock = (blocks: TextblockTarget[], options?: TextblockOptions) => {
useLayoutEffect(() => {
const cancelHandle = Textblock(blocks, { debug: true });
return () => {
if (cancelHandle && typeof cancelHandle === "function") {
cancelHandle();
}
};
}, [blocks, options]);
};
This can be used on its own in CSR components.
Optionally, also create a client component for better compatibility with SSR (e.g. Nextjs):
// Textblock.tsx
"use client";
import { useTextblock } from "@/hooks/useTextblock";
import { TextblockTarget } from "textblock";
export interface TextblockProps {
blocks: TextblockTarget[];
debug?: boolean;
}
export const Textblock = ({ blocks, debug }: TextblockProps) => {
useTextblock(blocks, { debug });
return <></>;
};
Then, simply add the Textblock component wherever you need it. I strongly suggest adding it as close to the element you're targeting as possible (versus adding one instance at the application root), so you're not querying a massive component tree for elements that may not exist on every page render.
For example,
export const Title = ({ title }: { title?: string }) => {
return title ? (
<>
<Textblock
blocks={[
{
container: "self",
minWidth: 320,
maxWidth: 1024,
fontSizeMinWidth: 3.2,
fontSizeMaxWidth: 3.7,
fontSizeUnits: "em",
lineHeightMinWidth: 0.95,
lineHeightMaxWidth: 1,
target: "h1"
}
]}
/>
<h1>{title}</h1>
</>
) : (
<></>
);
};
Cheers! 🍺
Hello! First of all, this framework seems pretty cool. Thank you for that!
But I have some problem using it in a React app.
Expected behaviour in App.js:
I guess this is the ES6 way to do it. This is mentioned here: https://github.com/glyphic-co/textblock/issues/7#issuecomment-364515428 Current behavior As it seems now, I have to do this in e.g. index.html the old fashioned way.