Repository: Silind-Software/direflow Branch: master Commit: dd1278dcbbfa Files: 144 Total size: 154.1 KB Directory structure: gitextract_rnz70h0d/ ├── .eslintrc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── pull_request_template.md │ └── workflows/ │ ├── build.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin/ │ └── direflow ├── cli/ │ ├── checkForUpdate.ts │ ├── cli.ts │ ├── create.ts │ ├── headline.ts │ ├── helpers/ │ │ ├── copyTemplate.ts │ │ ├── detectDireflowSetup.ts │ │ ├── nameFormat.ts │ │ └── writeNames.ts │ ├── messages.ts │ ├── questions.ts │ └── types/ │ ├── Command.ts │ ├── LangageOption.ts │ ├── Names.ts │ ├── QuestionOption.ts │ └── TemplateOption.ts ├── cypress/ │ ├── integration/ │ │ ├── basic_tests.ts │ │ ├── event_tests.ts │ │ ├── external_loader_test.ts │ │ ├── material_ui_test.ts │ │ ├── props_tests.ts │ │ ├── slot_tests.ts │ │ └── styled_components_test.ts │ ├── support/ │ │ ├── commands.js │ │ └── index.js │ ├── test-setup/ │ │ ├── direflow-config.json │ │ ├── direflow-webpack.js │ │ ├── jsconfig.json │ │ ├── jsconfig.paths.json │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index_prod.html │ │ └── src/ │ │ ├── component-exports.js │ │ ├── direflow-components/ │ │ │ └── test-setup/ │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── MaterialUI.js │ │ │ ├── StyledComponent.js │ │ │ ├── index.js │ │ │ └── test/ │ │ │ └── App.test.js │ │ └── index.js │ └── tsconfig.json ├── cypress.json ├── declarations.d.ts ├── package.json ├── packages/ │ ├── direflow-component/ │ │ ├── README.md │ │ ├── declarations.d.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── DireflowComponent.tsx │ │ │ ├── WebComponentFactory.tsx │ │ │ ├── components/ │ │ │ │ ├── EventContext.tsx │ │ │ │ └── Styled.tsx │ │ │ ├── decorators/ │ │ │ │ └── DireflowConfiguration.ts │ │ │ ├── helpers/ │ │ │ │ ├── asyncScriptLoader.ts │ │ │ │ ├── domControllers.ts │ │ │ │ ├── getSerialized.ts │ │ │ │ ├── polyfillHandler.ts │ │ │ │ ├── proxyRoot.tsx │ │ │ │ ├── registerPlugin.ts │ │ │ │ └── styleInjector.tsx │ │ │ ├── hooks/ │ │ │ │ └── useExternalSource.ts │ │ │ ├── index.ts │ │ │ ├── plugins/ │ │ │ │ ├── externalLoaderPlugin.ts │ │ │ │ ├── fontLoaderPlugin.ts │ │ │ │ ├── iconLoaderPlugin.ts │ │ │ │ ├── materialUiPlugin.tsx │ │ │ │ ├── plugins.ts │ │ │ │ └── styledComponentsPlugin.tsx │ │ │ └── types/ │ │ │ ├── DireflowConfig.ts │ │ │ ├── DireflowElement.ts │ │ │ ├── DireflowPromiseAlike.ts │ │ │ └── PluginRegistrator.ts │ │ └── tsconfig.json │ └── direflow-scripts/ │ ├── README.md │ ├── bin/ │ │ └── direflow-scripts │ ├── declarations.d.ts │ ├── direflow-jest.config.js │ ├── package.json │ ├── src/ │ │ ├── cli.ts │ │ ├── config/ │ │ │ └── config-overrides.ts │ │ ├── helpers/ │ │ │ ├── asyncScriptLoader.ts │ │ │ ├── entryResolver.ts │ │ │ ├── getDireflowConfig.ts │ │ │ ├── messages.ts │ │ │ └── writeTsConfig.ts │ │ ├── index.ts │ │ ├── template-scripts/ │ │ │ ├── entryLoader.ts │ │ │ └── welcome.ts │ │ └── types/ │ │ ├── ConfigOverrides.ts │ │ └── DireflowConfig.ts │ ├── tsconfig.json │ └── webpack.config.js ├── scripts/ │ ├── bash/ │ │ ├── setupLocal.sh │ │ └── startIntegrationTest.sh │ └── node/ │ ├── buildAll.js │ ├── cleanupAll.js │ ├── installAll.js │ └── updateVersion.js ├── templates/ │ ├── js/ │ │ ├── .eslintrc │ │ ├── README.md │ │ ├── direflow-config.json │ │ ├── direflow-webpack.js │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.css │ │ │ └── index.html │ │ └── src/ │ │ ├── component-exports.js │ │ ├── direflow-components/ │ │ │ └── direflow-component/ │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── index.js │ │ │ └── test/ │ │ │ └── App.test.js │ │ └── index.js │ └── ts/ │ ├── .eslintrc │ ├── README.md │ ├── direflow-config.json │ ├── direflow-webpack.js │ ├── package.json │ ├── public/ │ │ ├── index.css │ │ └── index.html │ ├── src/ │ │ ├── component-exports.ts │ │ ├── direflow-components/ │ │ │ └── direflow-component/ │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── index.tsx │ │ │ └── test/ │ │ │ └── App.test.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── tslint.json ├── test/ │ ├── detectDireflowSetup.test.ts │ ├── domController.test.ts │ ├── nameformats.test.ts │ └── writeNames.test.ts ├── tsconfig.eslint.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc ================================================ { "env": { "browser": true, "es6": true, "node": true }, "settings": { "pragma": "React", "version": "detect" }, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "airbnb-typescript" ], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 2018, "sourceType": "module", "project": "./tsconfig.eslint.json" }, "plugins": ["react", "@typescript-eslint"], "rules": { "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/interface-name-prefix": "off", "@typescript-eslint/no-this-alias": "off", "@typescript-eslint/no-explicit-any": "off", "react/prop-types": "off", "react/jsx-props-no-spreading": "off", "react/destructuring-assignment": "off", "react/display-name": "off", "import/no-extraneous-dependencies": "off", "import/no-unresolved": "off", "no-async-promise-executor": "off", "no-console": "off", "no-param-reassign": "off", "no-underscore-dangle": "off", "global-require": "off", "arrow-body-style": "off", "implicit-arrow-linebreak": "off", "object-curly-newline": "off", "lines-between-class-members": "off", "function-paren-newline": "off", "linebreak-style": "off", "operator-linebreak": "off", "jsx-quotes": "off", "no-prototype-builtins": "off", "consistent-return": "off", "max-len": ["warn", { "code": 120 }] } } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: If you found a bug in Direflow, please file a bug report title: '' labels: 'Bug' assignees: 'Silind' --- **Describe the bug** A clear and concise description of what the bug is. **To reproduce** Steps to reproduce the behavior: 1. Install '...' 2. Open '....' 3. Build '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Package Manager:** To install Direflow, I used... (npm / yarn / something else) **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for Direflow title: '' labels: 'Enhancement' assignees: 'Silind' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/pull_request_template.md ================================================ **What does this pull request introduce? Please describe** A clear and concise description of what this pull requests includes. Please add a reference to the related issue, if relevant. **How did you verify that your changes work as expected? Please describe** Please describe your methods of testing your changes. **Example** Please describe how we can try out your changes 1. Create a new '...' 2. Build with '...' 3. See '...' **Screenshots** If applicable, add screenshots to demonstrate your changes. **Version** Which version is your changes included in? Did you remember to update the version of Direflow as explained here: https://github.com/Silind-Software/direflow/blob/master/CONTRIBUTING.md#version ================================================ FILE: .github/workflows/build.yml ================================================ name: build on: push: branches: - master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: 12.x registry-url: 'https://registry.npmjs.org' - name: Prepare run: | sudo apt-get install lsof npm install codecov -g - name: Install run: | npm run clean:all npm run install:all - name: Codecov run: codecov -t ${{ secrets.CODECOV_TOKEN }} - name: Build run: | npm run build:all - name: Test run: | npm run test - name: Integration Test run: | npm run cypress:test - name: Create version patch run: npm run update-version patch - name: Publish direflow-cli to NPM run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - name: Publish direflow-component to NPM run: | cd packages/direflow-component npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - name: Publish direflow-scripts to NPM run: | cd packages/direflow-scripts npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: [pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: 12.x browser: chrome - name: Prepare run: | sudo apt-get install lsof - name: Install run: | npm run clean:all npm run install:all - name: Build run: | npm run build:all - name: Test run: | npm run test - name: Integration Test run: | npm run cypress:test ================================================ FILE: .gitignore ================================================ # Project setup specifics dist build yarn.lock config-overrides.js config-overrides.d.ts tsconfig.lib.json # Cypress cypress/fixtures cypress/plugins cypress/screenshots # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # Generated files .idea/**/contentModel.xml # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser .idea/ # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 *.iml modules.xml .idea/misc.xml *.ipr # Sonarlint plugin .idea/sonarlint ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # yarn Integrity file .yarn-integrity # dotenv environment variables file .env.test # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ ### OSX ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### VisualStudioCode ### .vscode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ### VisualStudioCode Patch ### # Ignore all local history of files .history ### WebStorm+all ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff # Generated files # Sensitive or high-churn files # Gradle # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/modules.xml # .idea/*.iml # .idea/modules # *.iml # *.ipr # CMake # Mongo Explorer plugin # File-based project format # IntelliJ # mpeltonen/sbt-idea plugin # JIRA plugin # Cursive Clojure plugin # Crashlytics plugin (for Android Studio and IntelliJ) # Editor-based Rest Client # Android studio 3.1+ serialized cache file ### WebStorm+all Patch ### # Ignores the whole .idea folder and all .iml files # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 # Sonarlint plugin ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/osx,node,windows,webstorm+all,intellij+all,visualstudiocode ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing or otherwise, unacceptable behavior may be reported by contacting the project team at contact@silind.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality concerning the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] [homepage]: https://contributor-covenant.org [version]: https://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Issues In the case of a bug report, a suggestions, or if you just need help, please feel very free to open an issue. For general issues, please use the following labels: - Something is not working as intended: `Bug` - Need help with something: `Help wanted` - Have a question: `Question` - Have a suggestion or want to request a feature: `Enhancement` - Does the question regard direflow-component: `Direflow Component` - Does the question regard direflow-cli: `Direflow CLI` ## Pull request Pull requests are really welcome! ### Version When doing a pull request, please make sure to include an new version in your PR. There are multiple packages that needs to be in sync, so in order to update the version of Direflow, please use the script: ```console npm run update-version ``` In order to create a version patch, use the command: ```console npm run update-version patch ``` ## Developing on Direflow Start by making a fork of the direflow repository, and clone it down. ### Install Now cd into the project folder and run the command: ```console npm run install:all ``` This command will install the project, the packages and all peer dependencies. ### Link In order to test your changes locally, you want to link the project using the command: ```console npm link ``` Now, in order to make sure all version-references are pointed to your local version of the project, use the command: ```console npm run update-version link ``` _NB: To do all of this in one command, you can use:_ ```console npm run setup-local ``` ### Build After applying your changes, build the project using the command: ```console npm run build:full ``` Now, test that the project is working by using the command: ```console direflow -v ``` This should give you the latest version with a '-link' appended: ```console Current version of direflow-cli: 3.4.3-link ``` In this way you know that the command `direflow` is using your local setup. Now, test your new functionality. > Note: After you have build the project using build:full, you may want to run install:all again before continuing to develop. ### Commit the changes Before committing your new changes, remember to change the version using the command: ```console npm run update-version ``` ### Create the PR Create a branch for your changes called '_feature/name-of-the-changes_'. Make a PR into the **development** branch on the direflow repository. ## Updating the docs If you introduced user-facing changes, please update the [direflow-docs](https://github.com/Silind-Software/direflow-docs) accordingly. ## Additional resources Check out the [Direflow Wiki](https://github.com/Silind-Software/direflow/wiki). Here you can find guides and know-how that will help you developing on Direflow. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2020 Silind Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![](https://silind-s3.s3.eu-west-2.amazonaws.com/direflow/gh-banner.png)](https://direflow.io/) [![NPM Version](https://img.shields.io/npm/v/direflow-cli)](https://www.npmjs.com/package/direflow-cli) [![Github License](https://img.shields.io/github/license/Silind-Software/direflow)](https://github.com/Silind-Software/direflow/blob/master/LICENSE) ![Build Status](https://github.com/Silind-Software/direflow/workflows/build/badge.svg) ![Code Coverage](https://img.shields.io/codecov/c/github/Silind-Software/direflow) # [direflow.io](https://direflow.io/) #### Set up a React App and build it as a Web Component > This setup is based on [*react-scripts*](https://www.npmjs.com/package/react-scripts) from [*create-react-app*](https://create-react-app.dev/docs/getting-started) > A walkthrough of the principles used in this setup, can be read [in this article](https://itnext.io/react-and-web-components-3e0fca98a593) ## Get started Start by downloading the cli: ```console npm i -g direflow-cli ``` ### Create a new Direflow Component ```console direflow create ``` This will bootstrap a new Direflow Component for you. Now use the following commands: ```console cd npm install npm start ``` Your Direflow Component will start running on `localhost:3000` and your browser opens a new window

#### See full documentation on [direflow.io](https://direflow.io) ================================================ FILE: bin/direflow ================================================ #!/usr/bin/env node require = require('esm')(module); const cli = require('../dist/cli'); cli.default(); ================================================ FILE: cli/checkForUpdate.ts ================================================ import chalk from 'chalk'; import { execSync } from 'child_process'; import { updateAvailable } from './messages'; const checkForUpdates = () => { const rootPackage = require('../package.json'); const buffer = execSync('npm view direflow-cli version'); const currentVersion = buffer.toString('utf8'); if (rootPackage.version.trim() !== currentVersion.trim()) { return chalk.white(updateAvailable(rootPackage.version.trim(), currentVersion.trim())); } return ''; }; export default checkForUpdates; ================================================ FILE: cli/cli.ts ================================================ import { program, Command } from 'commander'; import chalk from 'chalk'; import headline from './headline'; import { createDireflowSetup } from './create'; import checkForUpdates from './checkForUpdate'; import { showVersion } from './messages'; type TOptions = | 'small' | 'js' | 'ts' | 'tslint' | 'eslint' | 'npm'; type TParsed = Command & { [key in TOptions]?: true } & { desc: string }; export default function cli() { program .command('create [project-name]') .alias('c') .description('Create a new Direflow Setup') .option('-d, --desc ', 'Choose description for your project') .option('--js', 'Choose JavaScript Direflow template') .option('--ts', 'Choose TypeScript Direflow template') .option('--tslint', 'Use TSLint for TypeScript template') .option('--eslint', 'Use ESLint for TypeScript template') .option('--npm', 'Make the project an NPM module') .action(handleAction); program .description(chalk.magenta(headline)) .version(showVersion()) .helpOption('-h, --help', 'Show how to use direflow-cli') .option('-v, --version', 'Show the current version'); const [, , simpleArg] = process.argv; if (!simpleArg) { return program.help(); } if (['-v', '--version'].includes(simpleArg)) { console.log(checkForUpdates()); } program.parse(process.argv); } async function handleAction(name: string | undefined, parsed: TParsed) { const { js, ts, tslint, eslint, npm, desc: description } = parsed; let language: 'js' | 'ts' | undefined; let linter: 'eslint' | 'tslint' | undefined; if (js) { language = 'js'; } else if (ts) { language = 'ts'; } if (eslint) { linter = 'eslint'; } else if (tslint) { linter = 'tslint'; } await createDireflowSetup({ name, linter, language, description, npmModule: !!npm, }).catch((err) => { console.log(''); console.log(chalk.red('Unfortunately, something went wrong creating your Direflow Component')); console.log(err); console.log(''); }); } ================================================ FILE: cli/create.ts ================================================ import fs from 'fs'; import chalk from 'chalk'; import { chooseName, chooseDescription, chooseLanguage, chooseLinter, isNpmModule } from './questions'; import copyTemplate from './helpers/copyTemplate'; import { getNameFormats, createDefaultName } from './helpers/nameFormat'; import isDireflowSetup from './helpers/detectDireflowSetup'; import { writeProjectNames } from './helpers/writeNames'; import { moreInfoMessage, componentFinishedMessage } from './messages'; interface ISetupPresets { name?: string; description?: string; language?: 'js' | 'ts'; linter?: 'eslint' | 'tslint'; npmModule?: boolean; } export async function createDireflowSetup(preset: ISetupPresets = {}): Promise { if (isDireflowSetup()) { console.log( chalk.red('You are trying to create a new Direflow Setup inside an existing Direflow Setup.'), ); return; } if (!preset.name) { const { name } = await chooseName(); preset.name = name; } if (!preset.description) { const { description } = await chooseDescription(); preset.description = description; } if (!preset.language) { const { language } = await chooseLanguage(); preset.language = language; } if (!preset.linter) { const { linter } = await chooseLinter(preset.language); preset.linter = linter; } if (!preset.npmModule) { const { npmModule } = await isNpmModule(); preset.npmModule = npmModule; } const { name, description, language, linter, npmModule } = preset; const componentName = createDefaultName(name); const projectName = componentName; if (fs.existsSync(projectName)) { console.log(chalk.red(`The directory '${projectName}' already exists at the current location`)); return; } const projectDirectoryPath = await copyTemplate({ language, projectName, }); await writeProjectNames({ linter, projectDirectoryPath, description, npmModule, names: getNameFormats(componentName), type: 'direflow-component', }); console.log(chalk.greenBright(componentFinishedMessage(projectName))); console.log(chalk.blueBright(moreInfoMessage)); } export default createDireflowSetup; ================================================ FILE: cli/headline.ts ================================================ const headline = ` ██████╗ ██╗██████╗ ███████╗███████╗██╗ ██████╗ ██╗ ██╗ ██╔══██╗██║██╔══██╗██╔════╝██╔════╝██║ ██╔═══██╗██║ ██║ ██║ ██║██║██████╔╝█████╗ █████╗ ██║ ██║ ██║██║ █╗ ██║ ██║ ██║██║██╔══██╗██╔══╝ ██╔══╝ ██║ ██║ ██║██║███╗██║ ██████╔╝██║██║ ██║███████╗██║ ███████╗╚██████╔╝╚███╔███╔╝ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝`; export default headline; ================================================ FILE: cli/helpers/copyTemplate.ts ================================================ import { resolve } from 'path'; import fs from 'fs'; import ncp from 'ncp'; import { ITemplateOption } from '../types/TemplateOption'; const copyTemplate = async (options: ITemplateOption): Promise => { const currentDirectory = process.cwd(); const templateDirectory = resolve(__dirname, `../../templates/${options.language}`); const projectDirectory: string = await new Promise((projectResolve, reject) => { const projectDir = `${currentDirectory}/${options.projectName}`; fs.mkdir(projectDir, (err: any) => { if (err) { console.log(err); reject(new Error(`Could not create directory: ${projectDir}`)); } projectResolve(projectDir); }); }); await new Promise((ncpResolve, reject) => { ncp.ncp(templateDirectory, projectDirectory, (err) => { if (err) { console.log(err); reject(new Error('Could not copy template files')); } ncpResolve(true); }); }); await new Promise((renameResolve, reject) => { fs.rename( `${projectDirectory}/src/direflow-components/direflow-component`, `${projectDirectory}/src/direflow-components/${options.projectName}`, (err) => { if (err) { console.log(err); reject(new Error('Could not rename component folder')); } renameResolve(true); }, ); }); return projectDirectory; }; export default copyTemplate; ================================================ FILE: cli/helpers/detectDireflowSetup.ts ================================================ import fs from 'fs'; const isDireflowSetup = (currentDirectory = process.cwd()): boolean => { return fs.existsSync(`${currentDirectory}/direflow-webpack.js`); }; export default isDireflowSetup; ================================================ FILE: cli/helpers/nameFormat.ts ================================================ import to from 'to-case'; import { INames } from '../types/Names'; export const getNameFormats = (name: string): INames => { return { title: to.title(name), pascal: to.pascal(name), snake: to.slug(name), }; }; export const createDefaultName = (name: string) => { const snakeName = to.slug(name); if (!snakeName.includes('-')) { return `${snakeName}-component`; } return snakeName; }; ================================================ FILE: cli/helpers/writeNames.ts ================================================ import fs from 'fs'; import handelbars from 'handlebars'; import path from 'path'; import { INames } from '../types/Names'; const packageJson = require('../../package.json'); const { version } = packageJson; interface IWriteNameOptions { projectDirectoryPath: string; linter: 'eslint' | 'tslint'; packageVersion?: string; description: string; npmModule: boolean; names: INames; type: string; } type TWriteNameExtendable = Required>; interface IHandelbarData extends TWriteNameExtendable { defaultDescription: string; eslint: boolean; tslint: boolean; } export async function writeProjectNames({ type, names, description, linter, npmModule, projectDirectoryPath, packageVersion = version, }: IWriteNameOptions): Promise { const projectDirectory = fs.readdirSync(projectDirectoryPath); const defaultDescription = description || 'This project is created using Direflow'; const writeNames = projectDirectory.map(async (dirElement: string) => { const filePath = path.join(projectDirectoryPath, dirElement); if (fs.statSync(filePath).isDirectory()) { return writeProjectNames({ names, description, type, linter, npmModule, projectDirectoryPath: filePath, }); } if (linter !== 'tslint') { if (filePath.endsWith('tslint.json')) { return fs.unlinkSync(filePath); } } if (linter !== 'eslint') { if (filePath.endsWith('.eslintrc')) { return fs.unlinkSync(filePath); } } return changeNameInfile(filePath, { names, defaultDescription, type, packageVersion, npmModule, eslint: linter === 'eslint', tslint: linter === 'tslint', }); }); await Promise.all(writeNames).catch(() => console.log('Failed to write files')); } async function changeNameInfile(filePath: string, data: IHandelbarData): Promise { const changedFile = await new Promise((resolve, reject) => { fs.readFile(filePath, 'utf-8', (err, content) => { if (err) { reject(false); } const template = handelbars.compile(content); const changed = template(data); resolve(changed); }); }); await new Promise((resolve, reject) => { if ( typeof changedFile == 'string' ) { fs.writeFile(filePath, changedFile, 'utf-8', (err) => { if (err) { reject(); } resolve(true); }); } }); } export default writeProjectNames; ================================================ FILE: cli/messages.ts ================================================ import chalk from 'chalk'; import boxen from 'boxen'; export const componentFinishedMessage = (componentName: string) => ` Your Direflow Component is ready! To get started: cd ${componentName} npm install npm start The Direflow Component will be running at: ${chalk.magenta('localhost:3000')} `; export const moreInfoMessage = ` To learn more about Direflow, visit: https://direflow.io `; export const updateAvailable = (currentVersion: string, newVersion: string) => { const content = `There is a new version of direflow-cli available: ${chalk.greenBright(newVersion)}. You are currently running direflow-cli version: ${chalk.blueBright(currentVersion)}. Run '${chalk.magenta('npm i -g direflow-cli')}' to get the latest version.`; return boxen(content, { padding: 1, align: 'center', margin: 1 }); }; export const showVersion = () => { const packageJson = require('../package.json'); return `Current version of direflow-cli: ${packageJson.version} `; }; ================================================ FILE: cli/questions.ts ================================================ import inquirer from 'inquirer'; import { IQuestionOption } from './types/QuestionOption'; import { ILanguageOption } from './types/LangageOption'; export async function chooseLanguage(): Promise { console.log(''); return inquirer.prompt([ { type: 'list', name: 'language', message: 'Which language do you want to use?', choices: [ { value: 'js', name: 'JavaScript', }, { value: 'ts', name: 'TypeScript', }, ], }, ]); } export async function chooseLinter(language: 'js' | 'ts'): Promise<{ linter: 'eslint' | 'tslint' }> { if (language === 'js') { return { linter: 'eslint', }; } console.log(''); return inquirer.prompt([ { type: 'list', name: 'linter', message: 'Which linter do you want to use?', choices: [ { value: 'eslint', name: 'ESLint', }, { value: 'tslint', name: 'TSLint', }, ], }, ]); } export async function isNpmModule(): Promise<{ npmModule: boolean }> { console.log(''); return inquirer.prompt([ { type: 'list', name: 'npmModule', message: 'Do you want this to be an NPM module?', choices: [ { value: true, name: 'Yes', }, { value: false, name: 'No', }, ], }, ]); } export async function chooseName(): Promise { console.log(''); return inquirer.prompt([ { type: 'input', name: 'name', message: 'Choose a name for your Direflow Setup:', validate: (value: string) => { const pass = /^[a-zA-Z0-9-_]+$/.test(value); if (pass) { return true; } return 'Please enter a valid name'; }, }, ]); } export async function chooseDescription(): Promise { console.log(''); return inquirer.prompt([ { type: 'input', name: 'description', message: 'Give your Direflow Setup a description (optional)', }, ]); } ================================================ FILE: cli/types/Command.ts ================================================ export interface ICommand { [arg: string]: string; } ================================================ FILE: cli/types/LangageOption.ts ================================================ export interface ILanguageOption { language: 'js' | 'ts'; } ================================================ FILE: cli/types/Names.ts ================================================ export interface INames { title: string; pascal: string; snake: string; } ================================================ FILE: cli/types/QuestionOption.ts ================================================ export interface IQuestionOption { name: string; description: string; } ================================================ FILE: cli/types/TemplateOption.ts ================================================ export interface ITemplateOption { projectName: string; language: 'ts' | 'js'; } ================================================ FILE: cypress/integration/basic_tests.ts ================================================ describe('Running basic component', () => { before(() => { cy.visit('/'); }); it('should contain a custom element', () => { cy.get('basic-test').should('exist'); }); it('should have default componentTitle', () => { cy.shadowGet('basic-test') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Test Setup'); }); it('should have default sampleList items', () => { cy.shadowGet('basic-test') .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(0) .shadowContains('Item 1'); cy.shadowGet('basic-test') .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(1) .shadowContains('Item 2'); cy.shadowGet('basic-test') .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(2) .shadowContains('Item 3'); }); }); ================================================ FILE: cypress/integration/event_tests.ts ================================================ describe('Delegating events', () => { let eventHasFired = false; const fireEvent = () => { eventHasFired = true; }; before(() => { cy.visit('/'); cy.shadowGet('props-test').then((element) => { const [component] = element; component.addEventListener('test-click-event', fireEvent); }); }); it('should contain a custom element', () => { cy.get('event-test').should('exist'); }); it('should not have fired event', () => { expect(eventHasFired).to.equal(false); }); it('should fire event', () => { cy.shadowGet('props-test') .shadowFind('.app') .shadowFind('.button') .shadowClick().then(() => { expect(eventHasFired).to.equal(true); }); }); }); ================================================ FILE: cypress/integration/external_loader_test.ts ================================================ describe('Applying external resources', () => { before(() => { cy.visit('/'); }); it('should contain a custom element', () => { cy.get('external-loader-test').should('exist'); }); it('should have script tag in head', () => { cy.get('head script[src="https://code.jquery.com/jquery-3.3.1.slim.min.js"]').should('exist'); }); it('should have async script tag in head', () => { cy.get( 'head script[src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js"]', ).should('have.attr', 'async'); }); it('should contain external styles', () => { cy.shadowGet('external-loader-test') .shadowFind('#direflow_external-sources') .then((elem) => { const [element] = elem; const [link] = element.children; expect(link.href).to.equal( 'https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css', ); }); }); }); ================================================ FILE: cypress/integration/material_ui_test.ts ================================================ describe('Applying external resources', () => { before(() => { cy.visit('/'); }); it('should contain a custom element', () => { cy.get('material-ui-test').should('exist'); }); it('should contain styles', () => { cy.shadowGet('material-ui-test') .shadowFind('#direflow_material-ui-styles') .then((elem) => { const [element] = elem; expect(element).not.to.be.undefined; }); }); it('should style button correctly', () => { cy.shadowGet('material-ui-test') .shadowFind('#material-ui-button') .then((elem) => { const [element] = elem; const bgColor = window.getComputedStyle(element, null).getPropertyValue('background-color'); expect(bgColor).to.equal('rgb(63, 81, 181)'); }); }); }); ================================================ FILE: cypress/integration/props_tests.ts ================================================ describe('Using properties and attributes', () => { before(() => { cy.visit('/'); }); const assertSampleList = (id, assertions) => { cy.shadowGet(id) .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(0) .shadowContains(assertions[0]); cy.shadowGet(id) .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(1) .shadowContains(assertions[1]); cy.shadowGet(id) .shadowFind('.app') .shadowFind('div') .shadowEq(1) .shadowFind('.sample-text') .shadowEq(2) .shadowContains(assertions[2]); }; it('should contain a custom element', () => { cy.get('#props-test-1').should('exist'); }); it('should have default componentTitle', () => { cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Props Title'); }); it('should contain a custom element', () => { cy.get('#props-test-1').should('exist'); }); it('setting componentTitle property should update componentTitle', () => { cy.shadowGet('#props-test-1').then((element) => { const [component] = element; component.componentTitle = 'Update Title'; cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Update Title'); }); }); it('setting componenttitle attribute should update componentTitle', () => { cy.shadowGet('#props-test-1').then((element) => { const [component] = element; component.setAttribute('componenttitle', 'Any'); cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Any'); }); }); it('should update componentTitle with delay', () => { cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Any'); cy.wait(500); cy.shadowGet('#props-test-1').then((element) => { const [component] = element; component.componentTitle = 'Delay Title'; cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Delay Title'); }); }); it('should update sampleList items', () => { cy.shadowGet('#props-test-1').then((element) => { const [component] = element; const samples = ['New Item 1', 'New Item 2', 'New Item 3']; component.sampleList = samples; assertSampleList('#props-test-1', samples); }); }); it('should update sampleList items with delay', () => { const currentSamples = ['New Item 1', 'New Item 2', 'New Item 3']; assertSampleList('#props-test-1', currentSamples); cy.wait(500); cy.shadowGet('#props-test-1').then((element) => { const [component] = element; const newSamples = ['Delayed Item 1', 'Delayed Item 2', 'Delayed Item 3']; component.sampleList = newSamples; assertSampleList('#props-test-1', newSamples); }); }); it('should update based on falsy value', () => { cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('Delay Title'); cy.shadowGet('#props-test-1').then((element) => { const [component] = element; component.showTitle = false; cy.shadowGet('#props-test-1') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('no-title'); }); }); it('should update based on attribute without value', () => { cy.shadowGet('#props-test-2') .shadowFind('.app') .shadowFind('.hidden') .shadowContains('SHOW HIDDEN'); }); it('should treat attribute value "false" as boolean', () => { cy.shadowGet('#props-test-3') .shadowFind('.app') .shadowFind('.header-title') .shadowContains('no-title'); }); it('should parse attribute with JSON content', () => { assertSampleList('#props-test-4', ['test-1', 'test-2', 'test-3']); }); }); ================================================ FILE: cypress/integration/slot_tests.ts ================================================ describe('Running basic component without Shadow DOM', () => { before(() => { cy.visit('/'); }); it('should contain a custom element', () => { cy.get('slot-test').should('exist'); }); it('should contain a slotted element', () => { cy.shadowGet('slot-test') .shadowFind('.app') .shadowFind('.slotted-elements') .shadowFind('slot') .shadowEq(0) .then((element) => { const [slotted] = element[0].assignedNodes(); expect(slotted).contain('Slot Item 1'); }); }); it('should dynamically inject a slotted element', () => { cy.shadowGet('slot-test').then((element) => { const [component] = element; const newSlotted = document.createElement('div'); newSlotted.innerHTML = 'Slot Item 2'; newSlotted.slot = 'slotted-item-2'; component.appendChild(newSlotted); cy.shadowGet('slot-test') .shadowFind('.app') .shadowFind('.slotted-elements') .shadowFind('slot') .shadowEq(1) .then((element) => { const [slotted] = element[0].assignedNodes(); expect(slotted).contain('Slot Item 2'); }); }); }); }); ================================================ FILE: cypress/integration/styled_components_test.ts ================================================ describe('Applying external resources', () => { before(() => { cy.visit('/'); }); it('should contain a custom element', () => { cy.get('styled-components-test').should('exist'); }); it('should contain styles', () => { cy.shadowGet('styled-components-test') .shadowFind('#direflow_styled-components-styles') .then((elem) => { const [element] = elem; const [style] = element.children; expect(style.getAttribute('data-styled')).to.equal('active'); }); }); it('should style button correctly', () => { cy.shadowGet('styled-components-test') .shadowFind('#styled-component-button') .then((elem) => { const [element] = elem; const bgColor = window.getComputedStyle(element, null).getPropertyValue('background-color'); expect(bgColor).to.equal('rgb(255, 0, 0)'); }); }); }); ================================================ FILE: cypress/support/commands.js ================================================ import 'cypress-shadow-dom'; ================================================ FILE: cypress/support/index.js ================================================ import './commands'; ================================================ FILE: cypress/test-setup/direflow-config.json ================================================ { "build": { "filename": "direflowBundle.js" } } ================================================ FILE: cypress/test-setup/direflow-webpack.js ================================================ const { webpackConfig } = require('direflow-scripts'); const { aliasWebpack } = require("react-app-alias"); /** * Webpack configuration for Direflow Component * Additional webpack plugins / overrides can be provided here */ module.exports = (config, env) => { let useWebpackConfig = { ...webpackConfig(config, env), // Add your own webpack config here (optional) }; useWebpackConfig = aliasWebpack({})(useWebpackConfig); return useWebpackConfig; }; ================================================ FILE: cypress/test-setup/jsconfig.json ================================================ { "extends": "./jsconfig.paths.json", "compilerOptions": { "strict": true, "target": "es5", "lib": ["es5", "dom"], "types": ["cypress", "cypress-shadow-dom", "node"], "esModuleInterop": true, "moduleResolution": "node", "skipLibCheck": true, "noEmit": false }, "include": ["**/*.js"] } ================================================ FILE: cypress/test-setup/jsconfig.paths.json ================================================ { "compilerOptions": { "baseUrl": "src", "paths": { "react": ["./node_modules/react"], "react-dom": ["./node_modules/react-dom"], "styled-components": ["./node_modules/styled-components"], "@material-ui": ["./node_modules/@material-ui"] } } } ================================================ FILE: cypress/test-setup/package.json ================================================ { "name": "test-setup", "description": "This project is created using Direflow", "version": "1.0.0", "private": true, "scripts": { "start": "PORT=5000 direflow-scripts start", "build": "direflow-scripts build && cp ./public/index.css ./build && cp ./public/index_prod.html ./build/index.html", "serve": "serve ./build -l 5000" }, "dependencies": { "@material-ui/core": "^4.9.7", "direflow-component": "../../packages/direflow-component", "direflow-scripts": "../../packages/direflow-scripts", "eslint-plugin-react-hooks": "^4.5.0", "react": "17.0.2", "react-dom": "17.0.2", "react-lib-adler32": "^1.0.3", "react-scripts": "^4.0.3", "serve": "^13.0.2", "styled-components": "^5.0.1", "webfontloader": "^1.6.28" }, "devDependencies": { "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.30.0", "jest-environment-jsdom-fourteen": "^1.0.1", "react-app-alias": "^2.2.2", "react-app-rewired": "^2.2.1", "react-test-renderer": "17.0.2", "to-string-loader": "^1.2.0", "webpack-cli": "^4.9.2" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "jest": { "setupFilesAfterEnv": [ "direflow-scripts/direflow-jest.config.js" ] }, "config-overrides-path": "direflow-webpack.js" } ================================================ FILE: cypress/test-setup/public/index.css ================================================ body { padding: 0; margin: 0; width: 100vw; padding-top: 150px; display: flex; justify-content: center; align-items: center; background-color: #F6FAFA; } ================================================ FILE: cypress/test-setup/public/index.html ================================================ Test Setup
Slot Item 1
================================================ FILE: cypress/test-setup/public/index_prod.html ================================================ Test Setup
Slot Item 1
================================================ FILE: cypress/test-setup/src/component-exports.js ================================================ /** * In this file you can export components that will * be build as a pure React component library. * * Using the command `npm run build:lib` will * produce a folder `lib` with your React components. * * If you're not using a React component library, * this file can be safely deleted. */ import App from './direflow-components/test-setup/App'; export { App }; ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/App.css ================================================ .app { width: 400px; height: 575px; padding: 30px 60px; box-sizing: border-box; background-color: white; box-shadow: 0 4px 14px 4px #375c821c; font-family: 'Noto Sans JP', sans-serif; border-bottom: 5px solid #cad5e6; } .top { width: 100%; height: 50%; border-bottom: 2px solid #7998c7; display: flex; justify-content: center; align-items: center; } .bottom { width: 100%; height: 50%; border-top: 2px solid #7998c7; display: flex; flex-direction: column; justify-content: space-around; align-items: center; } .header-image { width: 165px; height: 165px; background: url('https://silind-s3.s3.eu-west-2.amazonaws.com/direflow/logo.svg'); background-size: contain; } .header-title { font-size: 34px; color: #5781C2; font-family: 'Advent Pro', sans-serif; } .sample-text { font-family: 'Noto Sans JP', sans-serif; font-size: 16px; color: #666; text-align: center; } .button { width: 150px; height: 45px; font-family: 'Noto Sans JP', sans-serif; font-size: 20px; font-weight: bold; background-color: #5781C2; color: white; box-shadow: 2px 2px 5px #16314d98; outline: none; border: 0; cursor: pointer; transition: 0.3s; } .button:hover { box-shadow: 4px 4px 8px #16314d63; background-color: #40558f; } ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/App.js ================================================ import React from 'react'; import { Styled, EventConsumer } from 'direflow-component'; import styles from './App.css'; const App = (props) => { const handleClick = (dispatch) => { const event = new Event('test-click-event'); dispatch(event); }; const renderSampleList = props.sampleList.map((sample) => (
{sample}
)); const title = props.showTitle ? props.componentTitle : 'no-title'; const hidden = props.showHidden ? 'SHOW HIDDEN' : null; return (
{title}
{renderSampleList}
{hidden}
{(dispatch) => ( )}
); }; App.defaultProps = { componentTitle: 'Test Setup', showTitle: true, showHidden: false, sampleList: ['Item 1', 'Item 2', 'Item 3'], }; export default App; ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/MaterialUI.js ================================================ import React from 'react'; import Button from '@material-ui/core/Button'; const MaterialUI = () => { return ( ); }; export default MaterialUI; ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/StyledComponent.js ================================================ import React from 'react'; import styled from 'styled-components'; const RedButton = styled.div` width: 100px; height: 50px; background-color: red; `; const StyledComponent = () => { return Styled Component Button; }; export default StyledComponent; ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/index.js ================================================ import { DireflowComponent } from 'direflow-component'; import App from './App'; import StyledComponent from './StyledComponent'; import MaterialUI from './MaterialUI'; DireflowComponent.createAll([ { component: App, configuration: { tagname: 'basic-test', }, }, { component: App, configuration: { tagname: 'props-test', }, }, { component: App, configuration: { tagname: 'event-test', }, }, { component: App, configuration: { tagname: 'slot-test', }, }, { component: App, configuration: { tagname: 'external-loader-test', }, plugins: [ { name: 'external-loader', options: { paths: [ 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css', { src: 'https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js', async: true, }, ], }, }, ], }, { component: StyledComponent, configuration: { tagname: 'styled-components-test', }, plugins: [ { name: 'styled-components', }, ], }, { component: MaterialUI, configuration: { tagname: 'material-ui-test', }, plugins: [ { name: 'material-ui', }, ], }, ]); ================================================ FILE: cypress/test-setup/src/direflow-components/test-setup/test/App.test.js ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import renderer from 'react-test-renderer'; import App from '../App'; const reactProps = { componentTitle: 'Component Test', sampleList: ['Mock', 'Test', 'Data'], }; it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); it('matches snapshot as expected', () => { const renderTree = renderer.create().toJSON(); expect(renderTree).toMatchSnapshot(); }); ================================================ FILE: cypress/test-setup/src/index.js ================================================ /** * This is the entry file of the Direflow setup. * * You can add any additional functionality here. * For example, this is a good place to hook into your * Web Component once it's mounted on the DOM. * * !This file cannot be removed. * It can be left blank if not needed. */ ================================================ FILE: cypress/tsconfig.json ================================================ { "compilerOptions": { "strict": true, "baseUrl": "../node_modules", "target": "es5", "lib": ["es5", "dom"], "types": ["cypress", "cypress-shadow-dom", "node"], "esModuleInterop": true, "moduleResolution": "node", "skipLibCheck": true, "noEmit": false }, "include": ["**/*.ts"] } ================================================ FILE: cypress.json ================================================ { "baseUrl": "http://localhost:5000", "video": false } ================================================ FILE: declarations.d.ts ================================================ declare module 'to-case'; ================================================ FILE: package.json ================================================ { "name": "direflow-cli", "version": "4.0.0", "description": "Official CLI for Direflow", "main": "dist/index.js", "scripts": { "build": "tsc", "test": "jest", "update-version": "node scripts/node/updateVersion.js", "setup-local": "./scripts/bash/setupLocal.sh", "clean:all": "node ./scripts/node/cleanupAll.js", "install:all": "node ./scripts/node/installAll.js", "build:all": "node ./scripts/node/buildAll.js && npm run install:all -- --no-deps", "build:full": "npm run clean:all && npm run install:all && npm run build:all && npm run clean:all -- --modules", "cypress:open": "cypress open", "cypress:run": "cypress run", "cypress:test": "./scripts/bash/startIntegrationTest.sh" }, "bin": { "direflow": "bin/direflow" }, "files": [ "bin/*", "dist/*", "templates/*" ], "repository": { "type": "git", "url": "git@github.com:Silind-Software/direflow.git" }, "keywords": [ "cli", "widget", "web component", "react", "typescript" ], "author": "Silind Software", "license": "MIT", "homepage": "https://direflow.io", "dependencies": { "boxen": "^6.2.1", "chalk": "4.1.2", "commander": "^9.3.0", "deepmerge": "^4.2.2", "esm": "^3.2.25", "handlebars": "^4.7.7", "inquirer": "^8.2.4", "mkdirp": "^1.0.4", "ncp": "^2.0.0", "rimraf": "^3.0.2", "to-case": "^2.0.0" }, "devDependencies": { "@types/inquirer": "^8.2.1", "@types/jest": "^28.1.0", "@types/jsdom": "^16.2.14", "@types/mkdirp": "^1.0.2", "@types/mock-fs": "^4.13.1", "@types/ncp": "^2.0.5", "@types/node": "^17.0.39", "@types/rimraf": "^3.0.2", "@types/webpack": "^5.28.0", "@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/parser": "^5.27.0", "cypress": "9.5.0", "cypress-shadow-dom": "^1.3.0", "eslint": "^8.17.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.30.0", "eslint-plugin-react-hooks": "^4.5.0", "jest": "^28.1.0", "jsdom": "^19.0.0", "memfs": "^3.4.7", "prettier": "^2.6.2", "start-server-and-test": "^1.14.0", "ts-jest": "^28.0.4", "typescript": "^4.7.3" }, "eslintConfig": { "extends": "react-app" }, "jest": { "roots": [ "/" ], "moduleFileExtensions": [ "ts", "js", "tsx" ], "transform": { "^.+\\.(ts|tsx)$": "ts-jest" }, "testMatch": [ "**/**/*.test.(ts|tsx)" ], "testPathIgnorePatterns": [ "/templates", "/packages", "/cypress" ], "collectCoverage": true } } ================================================ FILE: packages/direflow-component/README.md ================================================ # direflow-component ### This package includes configurations and scripts used by [Direflow](https://direflow.io) :warning: This package is not meant to be used on its own, but as a part of [Direflow](https://direflow.io). Please refer to the official webpage in order to get started. ================================================ FILE: packages/direflow-component/declarations.d.ts ================================================ declare module 'react-lib-adler32'; declare module '*.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.svg' { const content: any; export default content; } ================================================ FILE: packages/direflow-component/package.json ================================================ { "name": "direflow-component", "version": "4.0.0", "description": "Create Web Components using React", "main": "dist/index.js", "author": "Silind Software", "license": "MIT", "scripts": { "build": "tsc" }, "files": [ "dist" ], "repository": { "type": "git", "url": "git@github.com:Silind-Software/direflow.git" }, "homepage": "https://direflow.io", "dependencies": { "chalk": "4.1.2", "lodash": "^4.17.21", "react-lib-adler32": "^1.0.3", "sass": "^1.52.2", "webfontloader": "^1.6.28" }, "devDependencies": { "@types/lodash": "^4.14.182", "@types/react": "17.0.2", "@types/react-dom": "^17.0.2", "@types/webfontloader": "^1.6.34", "typescript": "^4.7.3" }, "peerDependencies": { "react": "17.0.2", "react-dom": "17.0.2" } } ================================================ FILE: packages/direflow-component/src/DireflowComponent.tsx ================================================ import WebComponentFactory from './WebComponentFactory'; import { IDireflowComponent } from './types/DireflowConfig'; import { DireflowElement } from './types/DireflowElement'; import includePolyfills from './helpers/polyfillHandler'; import DireflowPromiseAlike from './types/DireflowPromiseAlike'; let _resolve: Function; const callback = (element: HTMLElement) => { _resolve?.(element as DireflowElement); }; class DireflowComponent { /** * Create muliple Direflow Components * @param App React Component */ public static createAll(componentConfigs: IDireflowComponent[]): Array { return componentConfigs.map(DireflowComponent.create); } /** * Create Direflow Component * @param App React Component */ public static create(componentConfig: IDireflowComponent): DireflowPromiseAlike { const { component } = componentConfig; const plugins = component.plugins || componentConfig.plugins; const configuration = component.configuration || componentConfig.configuration; if (!component) { throw Error('Root component has not been set'); } if (!configuration) { throw Error('No configuration found'); } const componentProperties = { ...componentConfig?.properties, ...component.properties, ...component.defaultProps, }; const tagName = configuration.tagname || 'direflow-component'; const shadow = configuration.useShadow !== undefined ? configuration.useShadow : true; const anonymousSlot = configuration.useAnonymousSlot !== undefined ? configuration.useAnonymousSlot : false; (async () => { /** * TODO: This part should be removed in next minor version */ await Promise.all([includePolyfills({ usesShadow: !!shadow }, plugins)]); const WebComponent = new WebComponentFactory( componentProperties, component, shadow, anonymousSlot, plugins, callback, ).create(); customElements.define(tagName, WebComponent); })(); return { then: async (resolve?: (element: HTMLElement) => void) => { if (resolve) { _resolve = resolve; } }, }; } } export default DireflowComponent; ================================================ FILE: packages/direflow-component/src/WebComponentFactory.tsx ================================================ /* eslint-disable class-methods-use-this */ /* eslint-disable max-classes-per-file */ import React from 'react'; import ReactDOM from 'react-dom'; import _ from 'lodash'; import createProxyRoot from './helpers/proxyRoot'; import { IDireflowPlugin } from './types/DireflowConfig'; import { EventProvider } from './components/EventContext'; import { PluginRegistrator } from './types/PluginRegistrator'; import registeredPlugins from './plugins/plugins'; import getSerialized from './helpers/getSerialized'; class WebComponentFactory { constructor( private componentProperties: { [key: string]: unknown }, private rootComponent: React.FC | React.ComponentClass, private shadow?: boolean, private anonymousSlot?: boolean, private plugins?: IDireflowPlugin[], private connectCallback?: (element: HTMLElement) => void, ) { this.reflectPropertiesToAttributes(); } private componentAttributes: { [key: string]: { property: string; value: unknown; }; } = {}; /** * All properties with primitive values are added to attributes. */ private reflectPropertiesToAttributes() { Object.entries(this.componentProperties).forEach(([key, value]) => { if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'boolean') { return; } this.componentAttributes[key.toLowerCase()] = { property: key, value, }; }); } /** * Create new class that will serve as the Web Component. */ public create() { const factory = this; return class WebComponent extends HTMLElement { public initialProperties = _.cloneDeep(factory.componentProperties); public properties: { [key: string]: unknown } = {}; public hasConnected = false; constructor() { super(); this.transferInitialProperties(); this.subscribeToProperties(); } /** * Observe attributes for changes. * Part of the Web Component Standard. */ public static get observedAttributes() { return Object.keys(factory.componentAttributes); } /** * Web Component gets mounted on the DOM. */ public connectedCallback() { this.mountReactApp({ initial: true }); this.hasConnected = true; factory.connectCallback?.(this); } /** * When an attribute is changed, this callback function is called. * @param name name of the attribute * @param oldValue value before change * @param newValue value after change */ public attributeChangedCallback(name: string, oldValue: string, newValue: string) { if (!this.hasConnected) { return; } if (oldValue === newValue) { return; } if (!factory.componentAttributes.hasOwnProperty(name)) { return; } const propertyName = factory.componentAttributes[name].property; this.properties[propertyName] = getSerialized(newValue); this.mountReactApp(); } /** * When a property is changed, this callback function is called. * @param name name of the property * @param oldValue value before change * @param newValue value after change */ public propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown) { if (!this.hasConnected) { return; } if (oldValue === newValue) { return; } this.properties[name] = newValue; this.mountReactApp(); } /** * Web Component gets unmounted from the DOM. */ public disconnectedCallback() { ReactDOM.unmountComponentAtNode(this); } /** * Setup getters and setters for all properties. * Here we ensure that the 'propertyChangedCallback' will get invoked * when a property changes. */ public subscribeToProperties() { const propertyMap = {} as PropertyDescriptorMap; Object.keys(this.initialProperties).forEach((key: string) => { propertyMap[key] = { configurable: true, enumerable: true, get: (): unknown => { const currentValue = this.properties.hasOwnProperty(key) ? this.properties[key] : _.get(this.initialProperties, key); return currentValue; }, set: (newValue: unknown) => { const oldValue = this.properties.hasOwnProperty(key) ? this.properties[key] : _.get(this.initialProperties, key); this.propertyChangedCallback(key, oldValue, newValue); }, }; }); Object.defineProperties(this, propertyMap); } /** * Syncronize all properties and attributes */ public syncronizePropertiesAndAttributes() { Object.keys(this.initialProperties).forEach((key: string) => { if (this.properties.hasOwnProperty(key)) { return; } if (this.getAttribute(key) !== null) { this.properties[key] = getSerialized(this.getAttribute(key) as string); return; } this.properties[key] = _.get(this.initialProperties, key); }); } /** * Transfer initial properties from the custom element. */ public transferInitialProperties() { Object.keys(this.initialProperties).forEach((key: string) => { if (this.hasOwnProperty(key)) { this.properties[key] = this[key as keyof WebComponent]; } }); } /** * Apply plugins */ public applyPlugins(application: JSX.Element): [JSX.Element, Element[]] { const shadowChildren: Element[] = []; const applicationWithPlugins = registeredPlugins.reduce( (app: JSX.Element, currentPlugin: PluginRegistrator) => { const pluginResult = currentPlugin(this, factory.plugins, app); if (!pluginResult) { return app; } const [wrapper, shadowChild] = pluginResult; if (shadowChild) { shadowChildren.push(shadowChild); } return wrapper; }, application, ); return [applicationWithPlugins, shadowChildren]; } /** * Generate react props based on properties and attributes. */ public reactProps(): { [key: string]: unknown } { this.syncronizePropertiesAndAttributes(); return this.properties; } /** * Mount React App onto the Web Component */ public mountReactApp(options?: { initial: boolean }) { const anonymousSlot = factory.anonymousSlot ? React.createElement('slot') : undefined; const application = ( {React.createElement(factory.rootComponent, this.reactProps(), anonymousSlot)} ); const [applicationWithPlugins, shadowChildren] = this.applyPlugins(application); if (!factory.shadow) { ReactDOM.render(applicationWithPlugins, this); return; } let currentChildren: Node[] | undefined; if (options?.initial) { currentChildren = Array.from(this.children).map((child: Node) => child.cloneNode(true)); } const root = createProxyRoot(this, shadowChildren); ReactDOM.render({applicationWithPlugins}, this); if (currentChildren) { currentChildren.forEach((child: Node) => this.append(child)); } } /** * Dispatch an event from the Web Component */ public eventDispatcher = (event: Event) => { this.dispatchEvent(event); }; }; } } export default WebComponentFactory; ================================================ FILE: packages/direflow-component/src/components/EventContext.tsx ================================================ import { createContext } from 'react'; const EventContext = createContext(() => { /* Initially return nothing */ }); export const EventProvider = EventContext.Provider; export const EventConsumer = EventContext.Consumer; export { EventContext }; ================================================ FILE: packages/direflow-component/src/components/Styled.tsx ================================================ import React, { FC, Component, ReactNode, ComponentClass, CSSProperties } from 'react'; import Style from '../helpers/styleInjector'; type TStyles = string | string[] | CSSProperties | CSSProperties[]; interface IStyled { styles: TStyles; scoped?: boolean; children: ReactNode | ReactNode[]; } const Styled: FC = (props): JSX.Element => { let styles; if (typeof props.styles === 'string') { styles = (props.styles as CSSProperties).toString(); } else { styles = (props.styles as CSSProperties[]).reduce( (acc: CSSProperties, current: CSSProperties) => `${acc} ${current}` as CSSProperties, ); } return ( ); }; const withStyles = (styles: TStyles) => (WrappedComponent: ComponentClass | FC

) => { // eslint-disable-next-line react/prefer-stateless-function return class extends Component { public render(): JSX.Element { return (

); } }; }; export { withStyles, Styled }; ================================================ FILE: packages/direflow-component/src/decorators/DireflowConfiguration.ts ================================================ import { IDireflowComponent } from '../types/DireflowConfig'; function DireflowConfiguration(config: Partial) { return >( constructor: T & Partial, ) => { const decoratedConstructor = constructor; decoratedConstructor.configuration = config.configuration; decoratedConstructor.properties = config.properties; decoratedConstructor.plugins = config.plugins; return decoratedConstructor; }; } export default DireflowConfiguration; ================================================ FILE: packages/direflow-component/src/helpers/asyncScriptLoader.ts ================================================ type TBundle = { script: Element; hasLoaded: boolean }; declare global { interface Window { wcPolyfillsLoaded: TBundle[]; reactBundleLoaded: TBundle[]; } } const asyncScriptLoader = (src: string, bundleListKey: 'wcPolyfillsLoaded' | 'reactBundleLoaded'): Promise => { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.async = true; script.src = src; if (!window[bundleListKey]) { window[bundleListKey] = []; } const existingPolyfill = window[bundleListKey].find((loadedScript) => { return loadedScript.script.isEqualNode(script); }); if (existingPolyfill) { if (existingPolyfill.hasLoaded) { resolve(); } existingPolyfill.script.addEventListener('load', () => resolve()); return; } const scriptEntry = { script, hasLoaded: false, }; window[bundleListKey].push(scriptEntry); script.addEventListener('load', () => { scriptEntry.hasLoaded = true; resolve(); }); script.addEventListener('error', () => reject(new Error('Polyfill failed to load'))); document.head.appendChild(script); }); }; export default asyncScriptLoader; ================================================ FILE: packages/direflow-component/src/helpers/domControllers.ts ================================================ export const injectIntoShadowRoot = (webComponent: HTMLElement, element: Element): void => { const elementToPrepend = webComponent.shadowRoot || webComponent; if (existsIdenticalElement(element, elementToPrepend)) { return; } elementToPrepend.prepend(element); }; export const injectIntoHead = (element: Element) => { if (existsIdenticalElement(element, document.head)) { return; } document.head.append(element); }; export const stripStyleFromHead = (styleId: string) => { const allChildren = document.head.children; const style = Array.from(allChildren).find((child) => child.id === styleId); if (style) { document.head.removeChild(style); } }; export const existsIdenticalElement = (element: Element, host: Element | ShadowRoot): boolean => { const allChildren = host.children; const exists = Array.from(allChildren).some((child) => element.isEqualNode(child)); return exists; }; ================================================ FILE: packages/direflow-component/src/helpers/getSerialized.ts ================================================ const getSerialized = (data: string) => { if (data === '') { return true; } if (data === 'true' || data === 'false') { return data === 'true'; } try { const parsed = JSON.parse(data.replace(/'/g, '"')); return parsed; } catch (error) { return data; } }; export default getSerialized; ================================================ FILE: packages/direflow-component/src/helpers/polyfillHandler.ts ================================================ import { IDireflowPlugin } from '../types/DireflowConfig'; import asyncScriptLoader from './asyncScriptLoader'; type TWcPolyfillsLoaded = Array<{ script: Element; hasLoaded: boolean }>; declare global { interface Window { wcPolyfillsLoaded: TWcPolyfillsLoaded; } } let didIncludeOnce = false; const DEFAULT_SD = 'https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.4.1/bundles/webcomponents-sd.js'; const DEFAULT_CE = 'https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.4.1/bundles/webcomponents-ce.js'; const DEFAULT_AD = 'https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.4.1/custom-elements-es5-adapter.js'; const includePolyfills = async ( options: { usesShadow: boolean }, plugins: IDireflowPlugin[] | undefined, ) => { if (didIncludeOnce) { return; } const scriptsList = []; let useSD = ''; let useCE = ''; let useAD = ''; const polyfillLoaderPlugin = plugins?.find((plugin) => plugin.name === 'polyfill-loader'); if (polyfillLoaderPlugin) { console.warn( 'polyfill-loader plugin is deprecated. Use direflow-config.json instead.' + '\n' + 'See more: https://direflow.io/configuration', ); } const polyfillSD = process.env.DIREFLOW_SD ?? polyfillLoaderPlugin?.options?.use.sd; const polyfillCE = process.env.DIREFLOW_CE ?? polyfillLoaderPlugin?.options?.use.ce; const polyfillAdapter = process.env.DIREFLOW_ADAPTER ?? polyfillLoaderPlugin?.options?.use.adapter; const disableSD = polyfillSD === false; const disableCE = polyfillCE === false; const disableAD = polyfillAdapter === false; if (polyfillSD) { useSD = typeof polyfillSD === 'string' ? polyfillSD : DEFAULT_SD; } if (polyfillCE) { useCE = typeof polyfillCE === 'string' ? polyfillCE : DEFAULT_CE; } if (polyfillAdapter) { useAD = typeof polyfillAdapter === 'string' ? polyfillAdapter : DEFAULT_AD; } if (options.usesShadow && !disableSD) { scriptsList.push(asyncScriptLoader(useSD || DEFAULT_SD, 'wcPolyfillsLoaded')); } if (!disableCE) { scriptsList.push(asyncScriptLoader(useCE || DEFAULT_CE, 'wcPolyfillsLoaded')); } if (!disableAD) { scriptsList.push(asyncScriptLoader(useAD || DEFAULT_AD, 'wcPolyfillsLoaded')); } try { await Promise.all(scriptsList); didIncludeOnce = true; } catch (error) { console.error(error); } }; export default includePolyfills; ================================================ FILE: packages/direflow-component/src/helpers/proxyRoot.tsx ================================================ import React, { FC } from 'react'; import { createPortal } from 'react-dom'; interface IPortal { targetElement: ShadowRoot; children: React.ReactNode; } interface IShadowComponent { children: React.ReactNode | React.ReactNode[]; } interface IComponentOptions { webComponent: Element; mode: 'open' | 'closed'; shadowChildren: Element[]; } const Portal: FC = (props) => { const targetElement = (props.targetElement as unknown) as Element; return createPortal(props.children, targetElement); }; const createProxyComponent = (options: IComponentOptions) => { const ShadowRoot: FC = (props) => { const shadowedRoot = options.webComponent.shadowRoot || options.webComponent.attachShadow({ mode: options.mode }); options.shadowChildren.forEach((child) => { shadowedRoot.appendChild(child); }); return {props.children}; }; return ShadowRoot; }; const componentMap = new WeakMap>(); const createProxyRoot = ( webComponent: Element, shadowChildren: Element[], ): { [key in 'open' | 'closed']: React.FC } => { return new Proxy( { open: null, closed: null }, { get(_: unknown, mode: 'open' | 'closed') { if (componentMap.get(webComponent)) { return componentMap.get(webComponent); } const proxyComponent = createProxyComponent({ webComponent, mode, shadowChildren }); componentMap.set(webComponent, proxyComponent); return proxyComponent; }, }, ); }; export default createProxyRoot; ================================================ FILE: packages/direflow-component/src/helpers/registerPlugin.ts ================================================ import { IDireflowPlugin } from '../types/DireflowConfig'; import { PluginRegistrator } from '../types/PluginRegistrator'; const registerPlugin = (registrator: PluginRegistrator) => { return ( element: HTMLElement, plugins: IDireflowPlugin[] | undefined, app?: JSX.Element, ) => registrator(element, plugins, app); }; export default registerPlugin; ================================================ FILE: packages/direflow-component/src/helpers/styleInjector.tsx ================================================ import React, { Component, cloneElement, isValidElement } from 'react'; import adler32 from 'react-lib-adler32'; const isDevEnv = process.env.NODE_ENV !== 'production'; interface IProps { scoped?: boolean; } class Style extends Component { private scopeClassNameCache: { [key: string]: string } = {}; private scopedCSSTextCache: { [key: string]: string } = {}; private scoped = this.props.scoped !== undefined ? this.props.scoped : true; private pepper = ''; getStyleString = () => { if (this.props.children instanceof Array) { const styleString = this.props.children.filter( (child) => !isValidElement(child) && typeof child === 'string', ); if (styleString.length > 1) { throw new Error(`Multiple style objects as direct descedents of a Style component are not supported (${styleString.length} style objects detected): ${styleString[0]} `); } return styleString[0]; } if (typeof this.props.children === 'string' && !isValidElement(this.props.children)) { return this.props.children; } return null; }; getRootElement = () => { if (this.props.children instanceof Array) { const rootElement = this.props.children.filter((child) => isValidElement(child)); if (isDevEnv) { if (rootElement.length > 1) { console.log(rootElement); throw new Error(`Adjacent JSX elements must be wrapped in an enclosing tag (${rootElement.length} root elements detected)`); } if ( typeof rootElement[0] !== 'undefined' && this.isVoidElement((rootElement[0] as any).type) ) { throw new Error(`Self-closing void elements like ${(rootElement as any).type} must be wrapped in an enclosing tag. Reactive Style must be able to nest a style element inside of the root element and void element content models never allow it to have contents under any circumstances.`); } } return rootElement[0]; } if (isValidElement(this.props.children)) { return this.props.children; } return null; }; getRootSelectors = (rootElement: any) => { const rootSelectors = []; if (rootElement.props.id) { rootSelectors.push(`#${rootElement.props.id}`); } if (rootElement.props.className) { rootElement.props.className .trim() .split(/\s+/g) .forEach((className: string) => rootSelectors.push(className)); } if (!rootSelectors.length && typeof rootElement.type !== 'function') { rootSelectors.push(rootElement.type); } return rootSelectors; }; processCSSText = (styleString: any, scopeClassName?: string, rootSelectors?: any[]) => { return styleString .replace(/\s*\/\/(?![^(]*\)).*|\s*\/\*.*\*\//g, '') .replace(/\s\s+/g, ' ') .split('}') .map((fragment: any) => { const isDeclarationBodyPattern = /.*:.*;/g; const isLastItemDeclarationBodyPattern = /.*:.*(;|$|\s+)/g; const isAtRulePattern = /\s*@/g; const isKeyframeOffsetPattern = /\s*(([0-9][0-9]?|100)\s*%)|\s*(to|from)\s*$/g; return fragment .split('{') .map((statement: any, i: number, arr: any[]) => { if (!statement.trim().length) { return ''; } const isDeclarationBodyItemWithOptionalSemicolon = arr.length - 1 === i && statement.match(isLastItemDeclarationBodyPattern); if ( statement.match(isDeclarationBodyPattern) || isDeclarationBodyItemWithOptionalSemicolon ) { return this.escapeTextContentForBrowser(statement); } const selector = statement; if (scopeClassName && !/:target/gi.test(selector)) { if (!selector.match(isAtRulePattern) && !selector.match(isKeyframeOffsetPattern)) { return this.scopeSelector(scopeClassName, selector, rootSelectors); } return selector; } return selector; }) .join('{\n'); }) .join('}\n'); }; escaper = (match: any) => { const ESCAPE_LOOKUP: { [key: string]: string } = { '>': '>', '<': '<', }; return ESCAPE_LOOKUP[match]; }; escapeTextContentForBrowser = (text: string) => { const ESCAPE_REGEX = /[><]/g; return `${text}`.replace(ESCAPE_REGEX, this.escaper); }; scopeSelector = (scopeClassName: string, selector: string, rootSelectors?: any[]) => { const scopedSelector: string[] = []; const groupOfSelectorsPattern = /,(?![^(|[]*\)|\])/g; const selectors = selector.split(groupOfSelectorsPattern); selectors.forEach((selectorElement) => { let containsSelector; let unionSelector; if ( rootSelectors?.length && rootSelectors.some((rootSelector) => selectorElement.match(rootSelector)) ) { unionSelector = selectorElement; const escapedRootSelectors = rootSelectors?.map((rootSelector) => rootSelector.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), ); unionSelector = unionSelector.replace( new RegExp(`(${escapedRootSelectors?.join('|')})`), `$1${scopeClassName}`, ); containsSelector = this.scoped ? `${scopeClassName} ${selectorElement}` : selectorElement; scopedSelector.push(unionSelector, containsSelector); } else { containsSelector = this.scoped ? `${scopeClassName} ${selectorElement}` : selectorElement; scopedSelector.push(containsSelector); } }); if (!this.scoped && scopedSelector.length > 1) { return scopedSelector[1]; } return scopedSelector.join(', '); }; getScopeClassName = (styleString: any, rootElement: any) => { let hash = styleString; if (rootElement) { this.pepper = ''; this.traverseObjectToGeneratePepper(rootElement); hash += this.pepper; } return (isDevEnv ? 'scope-' : 's') + adler32(hash); }; traverseObjectToGeneratePepper = (obj: any, depth = 0) => { if (depth > 32 || this.pepper.length > 10000) return; Object.keys(obj).forEach((prop) => { const isPropReactInternal = /^[_$]|type|ref|^value$/.test(prop); if (!!obj[prop] && typeof obj[prop] === 'object' && !isPropReactInternal) { this.traverseObjectToGeneratePepper(obj[prop], depth + 1); } else if (!!obj[prop] && !isPropReactInternal && typeof obj[prop] !== 'function') { this.pepper += obj[prop]; } }); }; isVoidElement = (type: string) => [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', ].some((voidType) => type === voidType); createStyleElement = (cssText: string, scopeClassName: string) => { return (