├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE ├── README.md ├── lerna.json ├── package.json ├── packages ├── cra-template-wptheme-typescript │ ├── README.md │ ├── package.json │ ├── template.json │ └── template │ │ ├── README.md │ │ ├── gitignore │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── index.php │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── post_installer.php │ │ ├── robots.txt │ │ ├── screenshot.png │ │ └── style.css │ │ └── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── serviceWorker.ts │ │ └── setupTests.ts ├── cra-template-wptheme │ ├── README.md │ ├── package.json │ ├── template.json │ └── template │ │ ├── README.md │ │ ├── gitignore │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── index.php │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── post_installer.php │ │ ├── robots.txt │ │ ├── screenshot.png │ │ └── style.css │ │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ └── setupTests.js ├── create-react-wptheme-utils │ ├── create-react-wptheme-error-overlay │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── .npmrc │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ └── index.js │ │ └── webpack.config.js │ ├── fileFunctions.js │ ├── fileWatcherPlugin.js │ ├── getUserConfig.js │ ├── package.json │ ├── postInstallerInfo.js │ ├── shell-js.js │ ├── wpThemeClient.js │ ├── wpThemeErrorOverlay.js │ └── wpThemeServer.js └── create-react-wptheme │ ├── .npmignore │ ├── .npmrc │ ├── LICENSE │ ├── README.md │ ├── createReactWpTheme.js │ ├── index.js │ └── package.json └── screenshots ├── console-tab.png ├── network-tab.png └── view-source.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # .gitattributes snippet to force users to use same line endings for project. 2 | # 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # https://help.github.com/articles/dealing-with-line-endings/ 10 | # https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 11 | 12 | 13 | 14 | # These files are text and should be normalized (Convert crlf => lf) 15 | *.php text 16 | *.css text 17 | *.js text 18 | *.json text 19 | *.htm text 20 | *.html text 21 | *.xml text 22 | *.txt text 23 | *.ini text 24 | *.inc text 25 | *.pl text 26 | *.rb text 27 | *.py text 28 | *.scm text 29 | *.sql text 30 | .htaccess text 31 | *.sh text 32 | 33 | # These files are binary and should be left untouched 34 | # (binary is a macro for -text -diff) 35 | *.png binary 36 | *.jpg binary 37 | *.jpeg binary 38 | *.gif binary 39 | *.ico binary 40 | *.mov binary 41 | *.mp4 binary 42 | *.mp3 binary 43 | *.flv binary 44 | *.fla binary 45 | *.swf binary 46 | *.gz binary 47 | *.zip binary 48 | *.7z binary 49 | *.ttf binary 50 | *.pyc binary 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build 5 | .DS_Store 6 | *.tgz 7 | my-app* 8 | template/src/__tests__/__snapshots__/ 9 | npm-debug.log* 10 | /.changelog 11 | yarn* 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /.gitignore 3 | .gitattributes 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | shrinkwrap=false 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present, https://github.com/devloco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create React WP Theme 2 | 3 | ## Still Up To Date! 4 | 5 | May 6, 2020 6 |
7 | Just wanted to note that version [v3.4.1](https://github.com/facebook/create-react-app/releases/tag/v3.4.1) of [Create-React-App](https://create-react-app.dev/) is still the latest. 8 | 9 | If you have a theme made with an earlier version of `create-react-wptheme` and want to update it to the latest code, [just follow these instructions](#updating-existing-themes). 10 | 11 | --- 12 | 13 | ## Getting Started 14 | 15 | [Michael Soriano](https://github.com/michaelsoriano) is writing a tutorial for creating a theme using React. He uses `create-react-wptheme` for the initial setup. He provides a lot more detail than this `readme` and the screen shots are really helpful. 16 | 17 | Check it out: 18 |
19 | [Let's build a WordPress theme with React: Part 1 (Setup)](http://michaelsoriano.com/wordpress-theme-react-part-1-setup/) 20 | 21 | ## Readme Contents 22 | 23 | For more details check out the rest of this document. 24 | 25 | - [Creating a New Theme](#creating-a-new-theme) 26 | - [Updating Existing Themes](#updating-existing-themes) 27 | - [Updating TypeScript Types](#updating-typescript-types) 28 | - [Developing Your Theme](#developing-your-theme) 29 | - [React Tutorials](#react-tutorials) 30 | - [The Public Folder](#the-public-folder) 31 | - [Dev Configuration](#dev-configuration) 32 | - [HTTPS/SSL Support](#httpsssl-support) 33 | - [Deploying Your Theme](#deploying-your-theme) 34 | - [Dealing With Differing Paths Between DEV and PROD](#dealing-with-differing-paths-between-dev-and-prod) 35 | - [Goals](#goals) 36 | - [Acknowledgements](#acknowledgements) 37 | - [License](#license) 38 | 39 | ## Creating a New Theme 40 | 41 | To create a WordPress theme using `create-react-wptheme`, follow these steps. 42 | 43 | - Make sure your WordPress server is up and running. 44 | - Change dir into your WordPress themes folder (**this is just an example, use your real themes folder**). 45 | - Windows: `cd C:\xampp\htdocs\wordpress\wp-content\themes` 46 | - Mac or \*nix: `cd /xampp/htdocs/wordpress/wp-content/themes` 47 | - Use `npx create-react-wptheme` to make a new theme 48 | - For example: (**replace "my_react_theme" with whatever you want your theme to be named**): 49 | - `npx create-react-wptheme my_react_theme` 50 | - If you want to use TypeScript, then the command would be: 51 | - `npx create-react-wptheme my_react_theme --typescript` 52 | - When it finishes it'll tell you to change into your new theme's folder and run the Nodejs watcher (replace "my_react_theme" with the same name you used in the previous step): 53 | - `cd my_react_theme/react-src` 54 | - `npm run start` 55 | - That sets up the theme so that it can be seen in the WordPress admin area. 56 | - **Go there now and set your WordPress site to use this theme.** 57 | - View the site in your browser with the new theme. 58 | - **You must do this as it does some extra setup via PHP.** 59 | - **If you get PHP errors, most likely your web server doesn't have write access to your theme.** 60 | - Write access for your web server is only needed during this setup step. 61 | - **You can revoke write access after the setup has completed.** 62 | - Interested (paranoid?) about what it's doing? Check out the file: `/index.php` 63 | - When that's done you'll see: `Now, back in your command prompt, rerun the "start" script again...` 64 | - To do that, go back to your command prompt where you first ran `npm run start` and rerun that same command again. 65 | - In a few seconds you should see your browser load with the standard create-react-app page, but it's running as a WordPress theme! 66 | 67 | ## Updating Existing Themes 68 | 69 | When new versions of `Create-React-WPTheme` are released, you can easily upgrade an existing theme using a single command. 70 | 71 | - At a command prompt, navigate to your theme's `react-src` folder. 72 | - e.g. `/react-src` 73 | - If you use `npm` then run this command: 74 | - `npm install @devloco/react-scripts-wptheme@latest` 75 | - If you use `yarn` then run this command: 76 | - `yarn add @devloco/react-scripts-wptheme@latest` 77 | 78 | ### Updating TypeScript Types 79 | 80 | If your theme uses TypeScript, you'll need to modify the theme's `react-app-env.d.ts` file: 81 | 82 | - Navigate to the `/react-src/src` folder. 83 | - Open the `react-app-env.d.ts` file in your editor of choice. 84 | - Change the line in there to read (be sure to include the leading three slashes): 85 | - `/// ` 86 | - Save the change and you should be all set. 87 | 88 | ## Developing Your Theme 89 | 90 | ### React Tutorials 91 | 92 | If you're looking at a React tutorial on the web, you can use `create-react-wptheme` in place of `create-react-app`. 93 |
But you do have to remember that the React app code is one extra folder down inside your theme folder. 94 |
Notice that the final folder in this path is `react-src`: 95 | 96 | `/xampp/htdocs/wordpress/wp-content/themes//react-src` 97 | 98 | So for example, if the tutorial instructs you to edit the `src/App.js` file, then for you, that file would actually be located at: 99 | 100 | `/react-src/src/App.js` 101 | 102 | ### The Public Folder 103 | 104 | The authors of the original `create-react-app` say that using the "Public" folder (found at `react-src/public` in your new theme's folder) is a last ditch "escape hatch" for adding otherwise-hard-to-deal-with files. 105 | 106 | But for this project, I've decided to use it for all things that you'd put into a normal WordPress theme (e.g. functions.php, etc.). So any PHP, hard-coded CSS, and/or hard-coded JS (like other JS libraries that you'd like to reference globally (I don't judge)), can go into the public folder. 107 | 108 | **In addition, any changes made to CSS, JS, and PHP files in the Public folder will cause a rebuild to happen.** Which is unlike `create-react-app` which ignores most of the files in the Public folder. 109 | 110 | ### Dev Configuration 111 | 112 | After you've created a new theme, and all the setup is done, you'll find a file named `react-src/user.dev.json` that has some configuration options that you can mess with if you need to. 113 | 114 | ### HTTPS/SSL Support 115 | 116 | If you develop using SSL (i.e. HTTPS), then you might want to run the `Browser Refresh Server` under SSL as well. Especially if you use Firefox, see here: [Firefox Websocket security issue](https://stackoverflow.com/questions/11768221/firefox-websocket-security-issue). 117 | 118 | To configure the `Browser Refresh Server` to use SSL, follow these steps: 119 | 120 | - Assuming you've already created a theme using `create-react-wptheme`, change directory into the `react-src` folder in your theme's folder 121 | - Be sure to follow **all the instructions** under the **Usage** section at the top of this document. You need to complete the PHP portion of the setup before you can configure SSL. 122 | - Windows example: `cd C:\xampp\htdocs\wordpress\wp-content\themes\\react-src` 123 | - Mac or \*nix example: `cd /xampp/htdocs/wordpress/wp-content/themes//react-src` 124 | - Create a new folder to hold you're development SSL certificate and key. 125 | - All OSes: `mkdir ssl` 126 | - Change directory into the `ssl` folder 127 | - All OSes `cd ssl` 128 | - Then create the SSL certificate and key. 129 | - Windows: Install the [Linux Subsystem for Windows 10](https://docs.microsoft.com/en-us/windows/wsl/install-win10), then use the \*nix instructions below. 130 | - Mac, see here: https://ksearch.wordpress.com/2017/08/22/generate-and-import-a-self-signed-ssl-certificate-on-mac-osx-sierra/ 131 | - \*nix, see here (**also works with Linux Subsystem for Windows 10**): https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl 132 | - Depending on which process you use to create the certificate and key, the files you created might have different extensions than those mentioned below. 133 | - That's OK. 134 | - Sometimes both files have a `.pem` extension, or each file has a different extension like `.crt` and `.key`. 135 | - **Just be sure you know which file is the certificate and which is the key.** 136 | - There is a file named `user.dev.json` in the folder named `react-src` in your theme. 137 | - Open that file in your favorite text editor. 138 | - Change the "wpThemeServer" section to look like this (make sure it is proper JSON): 139 | - `"wpThemeServer": { "enable": true, "host": "127.0.0.1", "port": 8090, "sslCert": "ssl/localhost.crt", "sslKey": "ssl/localhost.key", "watchFile": "../index.php" },` 140 | - **NOTE** the `sslCert` and `sslKey` items. **Make sure the values point to your SSL certificate and key files.** 141 | - The paths to those files can be **full paths** or **relative paths** from the `react-src` folder (as shown above). 142 | - I like to set the `host` to `127.0.0.1` instead of "localhost". Supposedly the numeric address gets special treatment at the OS level as being mostly safe. 143 | - Back in your command prompt, change dir back to the `react-src` folder. 144 | - All OSes: `cd ..` 145 | - Start Node/Webpack JS watcher as you normally would: 146 | - All OSes: `npm run start` 147 | - Your theme should open in a new browser tab 148 | - If you need to add an SSL exception to your browser for your new certificate, there is a page running over HTTPS at the "host" and "port" you set in `user.dev.json` above. 149 | - For example, if you're using host `127.0.0.1` and port `8090` as shown above, then open your browser to: 150 | - https://127.0.0.1:8090/ 151 | - From there you'll get the standard browser warning about self-signed certificates and get the option to add an exception. 152 | - Once you've finished adding an exception to your browser, you'll need to refresh the tab with your development theme to force a reconnect to the `Browser Refresh Server`. 153 | 154 | ## Deploying Your Theme 155 | 156 | While you're actively developing your theme, the files are not optimized for production. Before you put your theme into production, you need to run the `build` command. 157 | 158 | Open a command prompt and change into the `react-src` folder of you theme and run this command: 159 | `npm run build` 160 | 161 | When that command finishes, your optimized files are located in a folder that can be deployed to your production server. 162 | 163 | Here's an example showing which folder to deploy to your server: 164 | 165 | - ...wp-content 166 | - themes 167 | - my-theme 168 | - my-theme `<- deploy this folder to your production server's themes folder` 169 | - react-src 170 | - !READY_TO_DEPLOY!.txt 171 | 172 | If you need to continue developing your theme, simply: 173 | 174 | - `cd react-src` 175 | - `npm run start` 176 | 177 | And all your theme files will reappear. 178 | 179 | ### Dealing With Differing Paths Between DEV and PROD 180 | 181 | The `react-src/user.prod.json` configuration file is read when you run `npm run build`. The only option in there is setting the "homepage" 182 | which controls the relative linking to files in your theme. The "homepage" setting in your main `package.json` file is used during development (and build by default). 183 | 184 | During development, this is probably what you want. But **if you know your production server has a different root**, then you can set the homepage to be different during a production build. 185 | 186 | For example: 187 | 188 | - Your WordPress dev server is running at http://localhost/wordpress 189 | - the homepage setting in your main package.json file will probably work just fine. 190 | - The homepage line in your main package.json will be something like: `"homepage": "/wordpress/wp-content/themes/"` 191 | - But you know that your production server runs WordPress from the root: http://mycoolblog.com/ 192 | - In this case you want to remove the `/wordpress` part, so set the "homepage" line in your `user.prod.json` file to: 193 | `"homepage": "/wp-content/themes/"` 194 | - Then run `npm run build` 195 | - **Note** at this point, **your theme will appear broken on your dev server**. But it should look as expected on your production server. 196 | 197 | ## Goals 198 | 199 | - Remove WebPackDevServer and use your WordPress dev server instead. 200 | - Also, do not proxy the WordPress server. 201 | - Thus removing CORS as a concern. 202 | - Maintain feature parity(ish) with `create-react-app` 203 | - Touch the original `react-scripts` as little as possible. 204 | - Add new files where possible. 205 | - This will make merges easier. 206 | 207 | ## Acknowledgements 208 | 209 | I'm grateful to the authors of existing related projects for their ideas and collaboration: 210 | 211 | - [create-react-app](https://github.com/facebook/create-react-app) 212 | 213 | The original. 214 | 215 | - [filewatcher-webpack-plugin](https://www.npmjs.com/package/filewatcher-webpack-plugin) 216 | 217 | I used this as an example for writing my own plugin for watching changes to the create-react-app "public" folder. 218 | 219 | ## License 220 | 221 | Create React WP Theme is open source software [licensed as MIT](https://github.com/devloco/create-react-wptheme/blob/master/LICENSE). 222 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "^3.19.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/README.md: -------------------------------------------------------------------------------- 1 | # cra-template-wptheme-typescript 2 | 3 | This is the official TypeScript template for [Create React WPTheme](https://github.com/devloco/create-react-wptheme). 4 | 5 | To use this template, add `--typescript` when creating a new wp theme. 6 | 7 | For example: 8 | 9 | ```sh 10 | npx create-react-wptheme my-theme --typescript 11 | ``` 12 | 13 | # Create React WP Theme 14 | 15 | See the latest [README](https://github.com/devloco/create-react-wptheme) on Github. 16 | 17 | ## Goals 18 | 19 | - Remove WebPackDevServer and use your WordPress dev server instead. 20 | - Also, do not proxy the WordPress server. 21 | - Thus removing CORS as a concern. 22 | - Maintain feature parity(ish) with `create-react-app` 23 | - Touch the original `react-scripts` as little as possible. 24 | - Add new files where possible. 25 | - This will make merges easier. 26 | 27 | ## Acknowledgements 28 | 29 | I'm grateful to the authors of existing related projects for their ideas and collaboration: 30 | 31 | - [create-react-app](https://github.com/facebook/create-react-app) 32 | 33 | The original. 34 | 35 | - [filewatcher-webpack-plugin](https://www.npmjs.com/package/filewatcher-webpack-plugin) 36 | 37 | I used this as an example for writing my own plugin for watching changes to the create-react-app "public" folder. 38 | 39 | ## License 40 | 41 | Create React WP Theme is open source software [licensed as MIT](https://github.com/devloco/create-react-wptheme/blob/master/LICENSE). 42 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-template-wptheme-typescript", 3 | "version": "3.4.1-wp.1", 4 | "keywords": [ 5 | "react", 6 | "create-react-app", 7 | "create-react-wptheme", 8 | "template", 9 | "typescript" 10 | ], 11 | "description": "The TypeScript template for Create React WPTheme.", 12 | "main": "template.json", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/devloco/create-react-wptheme.git", 16 | "directory": "packages/cra-template-wptheme-typescript" 17 | }, 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">=8.10" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/devloco/create-react-wptheme/issues" 24 | }, 25 | "files": [ 26 | "template", 27 | "template.json" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "dependencies": { 4 | "@testing-library/react": "^9.3.2", 5 | "@testing-library/jest-dom": "^4.2.4", 6 | "@testing-library/user-event": "^7.1.2", 7 | "@types/node": "^12.0.0", 8 | "@types/react": "^16.9.0", 9 | "@types/react-dom": "^16.9.0", 10 | "@types/jest": "^24.0.0", 11 | "typescript": "~3.7.2" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme-typescript/template/public/favicon.ico -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/index.php: -------------------------------------------------------------------------------- 1 | 30 | 31 | Now, back in your command prompt, rerun the "start" script again... 32 | 33 | 38 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme-typescript/template/public/logo192.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme-typescript/template/public/logo512.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/post_installer.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 34 | React WordPress Theme 35 | 36 | 37 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme-typescript/template/public/screenshot.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/public/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Theme Name: REPLACE_WITH_THEME_NAME 3 | Author: your-name 4 | Author URI: https://github.com/your-github 5 | Description: Create React WP Themes with no build configuration 6 | Version: 0.1 7 | License: GNU General Public License v2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | Tags: starter 10 | Text Domain: your-domain 11 | 12 | This theme, like WordPress, is licensed under the GPL. 13 | */ 14 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | 5 | function App() { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit react-src/src/App.tsx and save to reload. 12 |

13 | 14 | Learn React 15 | 16 |
17 |
18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme-typescript/template/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/README.md: -------------------------------------------------------------------------------- 1 | # cra-template-wptheme 2 | 3 | This is the official base template for [Create React WPTheme](https://github.com/devloco/create-react-wptheme). 4 | 5 | # Create React WP Theme 6 | 7 | See the latest [README](https://github.com/devloco/create-react-wptheme) on Github. 8 | 9 | ## Goals 10 | 11 | - Remove WebPackDevServer and use your WordPress dev server instead. 12 | - Also, do not proxy the WordPress server. 13 | - Thus removing CORS as a concern. 14 | - Maintain feature parity(ish) with `create-react-app` 15 | - Touch the original `react-scripts` as little as possible. 16 | - Add new files where possible. 17 | - This will make merges easier. 18 | 19 | ## Acknowledgements 20 | 21 | I'm grateful to the authors of existing related projects for their ideas and collaboration: 22 | 23 | - [create-react-app](https://github.com/facebook/create-react-app) 24 | 25 | The original. 26 | 27 | - [filewatcher-webpack-plugin](https://www.npmjs.com/package/filewatcher-webpack-plugin) 28 | 29 | I used this as an example for writing my own plugin for watching changes to the create-react-app "public" folder. 30 | 31 | ## License 32 | 33 | Create React WP Theme is open source software [licensed as MIT](https://github.com/devloco/create-react-wptheme/blob/master/LICENSE). 34 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-template-wptheme", 3 | "version": "3.4.1-wp.1", 4 | "keywords": [ 5 | "wordpress", 6 | "create-react-app", 7 | "wordpress-theme", 8 | "wordpress-development", 9 | "wordpress-starter-theme", 10 | "wordpress-api", 11 | "reactjs", 12 | "react", 13 | "react-theme", 14 | "wordpress-server", 15 | "react-tutorial", 16 | "react-wordpress-themes", 17 | "php", 18 | "template" 19 | ], 20 | "description": "The base template for Create React WPTheme.", 21 | "main": "template.json", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/devloco/create-react-wptheme.git" 25 | }, 26 | "license": "MIT", 27 | "engines": { 28 | "node": ">=8" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/devloco/create-react-wptheme/issues" 32 | }, 33 | "files": [ 34 | "template", 35 | "template.json" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "dependencies": { 4 | "@testing-library/react": "^9.3.2", 5 | "@testing-library/jest-dom": "^4.2.4", 6 | "@testing-library/user-event": "^7.1.2" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme/template/public/favicon.ico -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/index.php: -------------------------------------------------------------------------------- 1 | 30 | 31 | Now, back in your command prompt, rerun the "start" script again... 32 | 33 | 38 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme/template/public/logo192.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme/template/public/logo512.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/post_installer.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 34 | React WordPress Theme 35 | 36 | 37 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/packages/cra-template-wptheme/template/public/screenshot.png -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/public/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Theme Name: REPLACE_WITH_THEME_NAME 3 | Author: your-name 4 | Author URI: https://github.com/your-github 5 | Description: Create React WP Themes with no build configuration 6 | Version: 0.1 7 | License: GNU General Public License v2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | Tags: starter 10 | Text Domain: your-domain 11 | 12 | This theme, like WordPress, is licensed under the GPL. 13 | */ 14 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | 5 | function App() { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit react-src/src/App.js and save to reload. 12 |

13 | 14 | Learn React 15 | 16 |
17 |
18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /packages/cra-template-wptheme/template/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build 5 | .DS_Store 6 | *.tgz 7 | my-app* 8 | template/src/__tests__/__snapshots__/ 9 | npm-debug.log* 10 | yarn* 11 | /.changelog 12 | wpThemeErrorOverlay.js 13 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | *.test.js 3 | *.spec.js 4 | yarn* 5 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | shrinkwrap=false 3 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/README.md: -------------------------------------------------------------------------------- 1 | # react-scripts-wptheme-error-overlay 2 | 3 | [create-react-wptheme](https://github.com/devloco/create-react-wptheme) client with error overlay 4 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devloco/create-react-wptheme-error-overlay", 3 | "version": "3.4.1-wp.1", 4 | "description": "create-react-wptheme client with error overlay", 5 | "main": "wptheme-error-overlay.js", 6 | "scripts": { 7 | "build": "npx webpack" 8 | }, 9 | "author": "devloco", 10 | "license": "MIT", 11 | "homepage": "https://github.com/devloco/create-react-wptheme", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/devloco/create-react-wptheme.git" 15 | }, 16 | "files": [ 17 | "wpThemeErrorOverlay.js" 18 | ], 19 | "dependencies": { 20 | "react-dev-utils": "^10.2.1", 21 | "react-error-overlay": "^6.0.7" 22 | }, 23 | "devDependencies": { 24 | "@babel/code-frame": "^7.8.3", 25 | "@babel/core": "^7.9.0", 26 | "@webpack-cli/info": "^0.2.0", 27 | "babel-loader": "^8.1.0", 28 | "webpack": "^4.42.1", 29 | "webpack-cli": "^3.3.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | var stripAnsi = require("strip-ansi"); 11 | var ErrorOverlay = require("react-error-overlay"); 12 | var formatWebpackMessages = require("react-dev-utils/formatWebpackMessages"); 13 | 14 | // Remember some state related to hot module replacement. 15 | var isFirstCompilation = true; 16 | var mostRecentCompilationHash = null; 17 | var hasCompileErrors = false; 18 | 19 | // We need to keep track of if there has been a runtime error. 20 | // Essentially, we cannot guarantee application state was not corrupted by the 21 | // runtime error. To prevent confusing behavior, we forcibly reload the entire 22 | // application. This is handled below when we are notified of a compile (code 23 | // change). 24 | // See https://github.com/facebookincubator/create-react-app/issues/3096 25 | var hadRuntimeError = false; 26 | var runtimeOptions = { 27 | onError: function() { 28 | hadRuntimeError = true; 29 | }, 30 | filename: "/static/js/bundle.js" 31 | }; 32 | ErrorOverlay.startReportingRuntimeErrors(runtimeOptions); 33 | 34 | export function clearConsole() { 35 | // Clean up outdated compile errors, if any. 36 | if (typeof console !== "undefined" && typeof console.clear === "function") { 37 | console.clear(); 38 | } 39 | } 40 | 41 | export function handleSuccess() { 42 | // Successful compilation. 43 | clearConsole(); 44 | isFirstCompilation = false; 45 | hasCompileErrors = false; 46 | hadRuntimeError = false; 47 | } 48 | 49 | export function handleWarnings(warnings) { 50 | //clearConsole(); 51 | 52 | // var isHotUpdate = !isFirstCompilation; 53 | isFirstCompilation = false; 54 | hasCompileErrors = false; 55 | 56 | function printWarnings() { 57 | // Print warnings to the console. 58 | var formatted = formatWebpackMessages({ 59 | warnings: warnings, 60 | errors: [] 61 | }); 62 | 63 | if (typeof console !== "undefined" && typeof console.warn === "function") { 64 | for (var i = 0; i < formatted.warnings.length; i++) { 65 | if (i === 5) { 66 | console.warn("There were more warnings in other files.\n" + "You can find a complete log in the terminal."); 67 | break; 68 | } 69 | 70 | console.warn(stripAnsi(formatted.warnings[i])); 71 | } 72 | } 73 | } 74 | 75 | printWarnings(); 76 | } 77 | 78 | export function handleErrors(errors) { 79 | //clearConsole(); 80 | 81 | isFirstCompilation = false; 82 | hasCompileErrors = true; 83 | 84 | // Format webpack messages. 85 | var formatted = formatWebpackMessages({ 86 | errors: errors, 87 | warnings: [] 88 | }); 89 | 90 | if (Array.isArray(formatted.errors) && formatted.errors.length > 0) { 91 | // Only show the first error. 92 | ErrorOverlay.reportBuildError(formatted.errors[0]); 93 | } else { 94 | if (typeof console !== "undefined" && typeof console.error === "function") { 95 | console.error("UNKNOWN ERROR from react-scripts-wptheme-error-overlay:handleErrors:", errors); 96 | } 97 | } 98 | 99 | // Do not attempt to reload now. 100 | // We will reload on next success instead. 101 | } 102 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/create-react-wptheme-error-overlay/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | "use strict"; 8 | 9 | const path = require("path"); 10 | 11 | module.exports = { 12 | mode: "none", 13 | entry: path.join(__dirname, "src", "index.js"), 14 | watch: false, 15 | watchOptions: { 16 | aggregateTimeout: 600, 17 | ignored: [__dirname, "node_modules", "webpack.config.js", "wpThemeErrorOverlay.js"] 18 | }, 19 | output: { 20 | path: path.join(__dirname, ".."), 21 | filename: "wpThemeErrorOverlay.js", 22 | library: "wpThemeErrorOverlay", 23 | libraryTarget: "umd" 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | include: path.join(__dirname, "src"), 30 | use: "babel-loader" 31 | } 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/fileFunctions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs-extra"); 11 | const path = require("path"); 12 | const { rm, cp } = require("shelljs"); 13 | const wpThemePostInstallerInfo = require("@devloco/create-react-wptheme-utils/postInstallerInfo"); 14 | const wpThemeUserConfig = require("@devloco/create-react-wptheme-utils/getUserConfig"); //(paths, process.env.NODE_ENV); 15 | 16 | const _doNotEditFileName = "!DO_NOT_EDIT_THESE_FILES!.txt"; 17 | const _readyToDeployFileName = "!READY_TO_DEPLOY!.txt"; 18 | const _forBuild = () => { 19 | const nodeEnv = process.env.NODE_ENV; 20 | switch (nodeEnv) { 21 | case "dev": 22 | case "development": 23 | return false; 24 | case "build": 25 | case "prod": 26 | case "production": 27 | return true; 28 | default: 29 | console.log(chalk.red(`Unknown env.NODE_ENV: ${nodeEnv}`)); 30 | return false; 31 | } 32 | }; 33 | 34 | const fileFunctions = { 35 | copyPublicFolder: function(paths) { 36 | fs.copySync(paths.appPublic, paths.appBuild, { 37 | dereference: true, 38 | filter: (file) => file !== paths.appHtml && file.indexOf("index.html") == -1 && file.indexOf(wpThemePostInstallerInfo.postInstallerName) == -1 39 | }); 40 | }, 41 | copyToThemeFolder: function(paths) { 42 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 43 | const forBuild = _forBuild(); 44 | let actionPath = (forBuild && userConfig && userConfig.finalBuildPath) || ".."; 45 | 46 | const copyFrom = path.join(paths.appBuild, "/*"); 47 | 48 | if (forBuild === true) { 49 | fileFunctions.cleanThemeFolder(paths, true); 50 | fileFunctions.deleteDeployFolder(paths); 51 | 52 | const themeName = require(paths.appPackageJson).name; 53 | actionPath = (userConfig && userConfig.finalBuildPath) || path.join("..", themeName); 54 | fs.ensureDirSync(actionPath); 55 | } 56 | 57 | cp("-rf", copyFrom, actionPath); 58 | }, 59 | cleanThemeFolder: function(paths) { 60 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 61 | const forBuild = _forBuild(); 62 | let actionPath = (forBuild && userConfig && userConfig.finalBuildPath) || ".."; 63 | 64 | rm("-rf", path.join(actionPath, "static")); 65 | 66 | if (forBuild === true) { 67 | const doNotEditFile = path.join(actionPath, _doNotEditFileName); 68 | rm("-f", doNotEditFile); 69 | 70 | const readyToDeployFile = path.join(actionPath, _readyToDeployFileName); 71 | rm("-f", readyToDeployFile); 72 | 73 | const assetManifest = path.join(actionPath, "asset-manifest*"); 74 | rm("-r", assetManifest); 75 | 76 | const favIconIco = path.join(actionPath, "favicon.ico"); 77 | rm("-r", favIconIco); 78 | 79 | const indexPhp = path.join(actionPath, "index.php"); 80 | rm("-r", indexPhp); 81 | 82 | const logoFiles = path.join(actionPath, "logo*"); 83 | rm("-r", logoFiles); 84 | 85 | const precacheFiles = path.join(actionPath, "precache*"); 86 | rm("-r", precacheFiles); 87 | 88 | const manifestJson = path.join(actionPath, "manifest.json"); 89 | rm("-r", manifestJson); 90 | 91 | const robotsTxt = path.join(actionPath, "robots.txt"); 92 | rm("-r", robotsTxt); 93 | 94 | const screenShotPng = path.join(actionPath, "screenshot.png"); 95 | rm("-r", screenShotPng); 96 | 97 | const serviceWorker = path.join(actionPath, "service-worker.js"); 98 | rm("-r", serviceWorker); 99 | 100 | const styleCss = path.join(actionPath, "style.css"); 101 | rm("-r", styleCss); 102 | } 103 | }, 104 | deleteDeployFolder: function(paths) { 105 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 106 | const forBuild = _forBuild(); 107 | const themeName = require(paths.appPackageJson).name; 108 | 109 | let deployFolder = (forBuild && userConfig && userConfig.finalBuildPath) || path.join("..", themeName); 110 | if (fs.existsSync(deployFolder)) { 111 | var files = fs.readdirSync(deployFolder); 112 | files.forEach((file) => { 113 | if (file !== "react-src") { 114 | const fileDir = path.join(deployFolder, file); 115 | rm("-rf", fileDir); 116 | } 117 | }); 118 | } 119 | }, 120 | setupCopyToThemeFolder: function(paths) { 121 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 122 | const forBuild = _forBuild(); 123 | let actionPath = (forBuild && userConfig && userConfig.finalBuildPath) || ".."; 124 | 125 | const indexPhp = path.join(paths.appPublic, "index.php"); 126 | cp("-rf", indexPhp, actionPath); 127 | 128 | const styleCss = path.join(paths.appPublic, "style.css"); 129 | cp("-rf", styleCss, actionPath); 130 | 131 | const screenShotPng = path.join(paths.appPublic, "screenshot.png"); 132 | cp("-rf", screenShotPng, actionPath); 133 | 134 | const favIconIco = path.join(paths.appPublic, "favicon.ico"); 135 | cp("-rf", favIconIco, actionPath); 136 | }, 137 | writeDoNotEditFile: function(paths) { 138 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 139 | const forBuild = _forBuild(); 140 | let actionPath = (forBuild && userConfig && userConfig.finalBuildPath) || ".."; 141 | const readyToDeployFile = path.join(actionPath, _readyToDeployFileName); 142 | fs.access(readyToDeployFile, fs.constants.F_OK, (err) => { 143 | if (!err) { 144 | rm("-f", readyToDeployFile); 145 | } 146 | }); 147 | 148 | let doNotEditContent = `Instead, edit the files in the "react-src/src" and "react-src/public" folders.`; 149 | doNotEditContent += "\nThese files are overwritten by Webpack every time you make edits to the files in those folders."; 150 | doNotEditContent += "\nYou will lose all changes made to these files when that happens."; 151 | 152 | const doNotEditFile = path.join(actionPath, _doNotEditFileName); 153 | fs.access(doNotEditFile, fs.constants.F_OK, (err) => { 154 | if (err) { 155 | fs.writeFile(doNotEditFile, doNotEditContent, "utf8", (err) => {}); 156 | } 157 | }); 158 | }, 159 | writeReadyToDeployFile: function(paths) { 160 | const userConfig = wpThemeUserConfig(paths, process.env.NODE_ENV); 161 | const forBuild = _forBuild(); 162 | let actionPath = (forBuild && userConfig && userConfig.finalBuildPath) || ".."; 163 | const doNotEditFile = path.join(actionPath, _doNotEditFileName); 164 | fs.access(doNotEditFile, fs.constants.F_OK, (err) => { 165 | if (!err) { 166 | rm("-f", doNotEditFile); 167 | } 168 | }); 169 | 170 | const themeName = require(paths.appPackageJson).name; 171 | let readyToDeployContent = `The theme named "${themeName}" is ready to deploy to your production server.`; 172 | readyToDeployContent += '\n\nIf you need to continue developing your theme, simply change to the "react-src" folder and type the command: npm run start'; 173 | 174 | const readyToDeployFile = path.join(actionPath, _readyToDeployFileName); 175 | fs.access(readyToDeployFile, fs.constants.F_OK, (err) => { 176 | if (err) { 177 | fs.writeFile(readyToDeployFile, readyToDeployContent, "utf8", (err) => {}); 178 | } 179 | }); 180 | } 181 | }; 182 | 183 | module.exports = fileFunctions; 184 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/fileWatcherPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /// 9 | /// big thanks to: 10 | /// https://www.npmjs.com/package/filewatcher-webpack-plugin 11 | /// 12 | /// this plugin causes WebPack to do a rebuild by touching a file under WebPack's control when 13 | /// a file *not* under WebPack's control changes. 14 | /// 15 | /// Usage example: (to watch all JS, CSS and PHP files under the "public" folder in a create-react-wptheme project (or create-react-app project)) 16 | /// NOTE: "touchFile" is used to force WebPack to do a rebuild. It must be a file that WebPack is watching. 17 | /// 18 | /// const FileWatcherPlugin = require("./fileWatcherPlugin.wptheme"); 19 | /// ... 20 | /// plugins: [ 21 | // new FileWatcherPlugin({ 22 | // touchFile: "./public/index.php", // create-react-wptheme emits index.php and thus it is watched by WebPack... 23 | // ignored: "./public/index.php", // ... so no need to watch it here as well. Without ignoring it, you'd cause infinite build loops. 24 | // watchFileGlobs: ["./public/**/*.js", "./public/**/*.css", "./public/**/*.php"] 25 | // }), 26 | /// ], 27 | /// ... 28 | /// 29 | 30 | "use strict"; 31 | 32 | const chokidar = require("chokidar"); 33 | const { touch } = require("shelljs"); 34 | 35 | function FileWatcherPlugin(options) { 36 | this.options = options; 37 | } 38 | 39 | FileWatcherPlugin.prototype.apply = function (compiler) { 40 | if (!this.options) { 41 | return; 42 | } 43 | 44 | const options = this.options; 45 | const touchFile = options.touchFile; 46 | const forceBuild = function (touchFileName) { 47 | // -c to not create a file if one doesn't already exist. 48 | // Remember this file needs to be watched by WebPack, thus it should already exist. 49 | touch("-c", touchFileName); 50 | }; 51 | 52 | compiler.hooks.done.tap("FileWatcherPlugin", function (compilation) { 53 | var watcher = chokidar.watch(options.watchFileGlobs, { 54 | persistent: options.persistance || true, 55 | ignored: options.ignored || false, 56 | ignoreInitial: options.ignoreInitial || false, 57 | followSymlinks: options.followSymlinks || true, 58 | cwd: options.cwd || ".", 59 | disableGlobbing: options.disableGlobbing || false, 60 | usePolling: options.usePolling || false, 61 | interval: options.interval || 100, 62 | binaryInterval: options.binaryInterval || 300, 63 | alwaysStat: options.alwaysStat || false, 64 | depth: options.depth || 99, 65 | awaitWriteFinish: { 66 | stabilityThreshold: options.stabilityThreshold || 250, 67 | pollInterval: options.pollInterval || 100, 68 | }, 69 | 70 | ignorePermissionErrors: options.ignorePermissionErrors || false, 71 | atomic: options.atomic || true, 72 | }); 73 | 74 | watcher 75 | .on( 76 | "add", 77 | options.onAddCallback || 78 | function (path) { 79 | //forceBuild(touchFile); // causes infinite loops for "add" 80 | return null; 81 | } 82 | ) 83 | .on( 84 | "change", 85 | options.onChangeCallback || 86 | function (path) { 87 | console.log(`\n\n FileWatcherPlugin CHANGE for: ${path} \n\n`); 88 | forceBuild(touchFile); 89 | } 90 | ) 91 | .on( 92 | "unlink", 93 | options.onUnlinkCallback || 94 | function (path) { 95 | // console.log(`\n\n FileWatcherPlugin UNLINK for: ${path} \n\n`); 96 | forceBuild(touchFile); 97 | } 98 | ); 99 | 100 | watcher 101 | .on( 102 | "addDir", 103 | options.onAddDirCallback || 104 | function (path) { 105 | // console.log(`\n\n FileWatcherPlugin ADDDIR for: ${path} \n\n`); 106 | forceBuild(touchFile); 107 | } 108 | ) 109 | .on( 110 | "unlinkDir", 111 | options.unlinkDirCallback || 112 | function (path) { 113 | // console.log(`\n\n FileWatcherPlugin UNLINKDIR for: ${path} \n\n`); 114 | forceBuild(touchFile); 115 | } 116 | ) 117 | .on( 118 | "error", 119 | options.onErrorCallback || 120 | function (error) { 121 | // console.log(`FileWatcherPlugin error: ${error}`); 122 | return null; 123 | } 124 | ) 125 | .on( 126 | "ready", 127 | options.onReadyCallback || 128 | function () { 129 | console.log("Watching for changes in the Public folder." /*, watcher.getWatched()*/); 130 | } 131 | ) 132 | .on( 133 | "raw", 134 | options.onRawCallback || 135 | function (event, path, details) { 136 | return null; 137 | } 138 | ); 139 | }); 140 | }; 141 | 142 | module.exports = FileWatcherPlugin; 143 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/getUserConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs-extra"); 11 | const chalk = require("chalk"); 12 | const path = require("path"); 13 | const wpThemePostInstallerInfo = require("@devloco/create-react-wptheme-utils/postInstallerInfo"); 14 | 15 | const _userDevConfigName = "user.dev.json"; 16 | const _userProdConfigName = "user.prod.json"; 17 | 18 | function _writeUserConfig(paths, configName, configString) { 19 | let configPath = path.join(paths.appPath, configName); 20 | fs.writeFileSync(configPath, configString); 21 | } 22 | 23 | function _getUserConfig(paths, configName, defaultConfig) { 24 | let userConfig = null; 25 | try { 26 | userConfig = require(path.join(paths.appPath, configName)); 27 | } catch (err) { 28 | userConfig = JSON.stringify(defaultConfig, null, 4); 29 | // Issue 45; Always write the dev config on error; only write the prod config if it is complete. 30 | if (configName === _userDevConfigName || (configName === _userProdConfigName && defaultConfig && typeof defaultConfig.homepage === "string")) { 31 | _writeUserConfig(paths, configName, userConfig); 32 | } 33 | return defaultConfig; 34 | } 35 | 36 | return userConfig; 37 | } 38 | 39 | module.exports = function (paths, nodeEnv) { 40 | const appPackageJson = require(paths.appPackageJson); 41 | 42 | const defaultUserDevConfig = { 43 | fileWatcherPlugin: { 44 | touchFile: "./public/index.php", 45 | ignored: "./public/index.php", 46 | watchFileGlobs: ["./public/**/*.js", "./public/**/*.css", "./public/**/*.php"], 47 | }, 48 | wpThemeServer: { 49 | enable: true, 50 | host: "127.0.0.1", 51 | port: 8090, 52 | sslCert: null, 53 | sslKey: null, 54 | watchFile: "../index.php", 55 | }, 56 | injectWpThemeClient: { 57 | override: null, 58 | file: "./build/index.php", 59 | }, 60 | }; 61 | 62 | const defaultUserProdConfig = { 63 | finalBuildPath: null, 64 | homepage: appPackageJson.homepage, 65 | }; 66 | 67 | // Create both files ASAP. 68 | if (!wpThemePostInstallerInfo.postInstallerExists(paths)) { 69 | _getUserConfig(paths, _userDevConfigName, defaultUserDevConfig); 70 | _getUserConfig(paths, _userProdConfigName, defaultUserProdConfig); 71 | } 72 | 73 | if (wpThemePostInstallerInfo.postInstallerExists(paths)) { 74 | return null; 75 | } 76 | 77 | if (typeof nodeEnv !== "string") { 78 | nodeEnv = process.env.NODE_ENV; 79 | } 80 | 81 | switch (nodeEnv) { 82 | case "dev": 83 | case "development": 84 | return _getUserConfig(paths, _userDevConfigName, defaultUserDevConfig); 85 | case "build": 86 | case "prod": 87 | case "production": 88 | return _getUserConfig(paths, _userProdConfigName, defaultUserProdConfig); 89 | default: 90 | console.log(chalk.red(`Unknown env.NODE_ENV: ${nodeEnv}`)); 91 | return null; 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devloco/create-react-wptheme-utils", 3 | "version": "3.4.1-wp.2", 4 | "description": "Utilities used by create-react-wptheme.", 5 | "engines": { 6 | "node": ">=8" 7 | }, 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/devloco/create-react-wptheme" 14 | }, 15 | "author": "devloco", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/devloco/create-react-wptheme/issues" 19 | }, 20 | "files": [ 21 | "fileFunctions.js", 22 | "fileWatcherPlugin.js", 23 | "getUserConfig.js", 24 | "postInstallerInfo.js", 25 | "shell-js.js", 26 | "wpThemeClient.js", 27 | "wpThemeErrorOverlay.js", 28 | "wpThemeServer.js" 29 | ], 30 | "dependencies": { 31 | "chalk": "2.4.2", 32 | "chokidar": "^3.3.1", 33 | "shelljs": "^0.8.3", 34 | "fs-extra": "^8.1.0", 35 | "ws": "^7.2.3" 36 | }, 37 | "devDependencies": {} 38 | } 39 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/postInstallerInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | const postInstallerName = "post_installer.php"; 13 | 14 | class PostInstallerException { 15 | constructor(message) { 16 | this.message = message; 17 | this.name = "PostInstallerException"; 18 | } 19 | } 20 | 21 | const postInstallerInfo = { 22 | postInstallerExists: function(paths) { 23 | if (!paths) { 24 | throw new PostInstallerException("'paths' not provided."); 25 | } 26 | 27 | try { 28 | fs.accessSync(path.join(paths.appPublic, postInstallerName), fs.constants.F_OK); 29 | return true; 30 | } catch (err) { 31 | return false; 32 | } 33 | }, 34 | postInstallerName: postInstallerName 35 | }; 36 | 37 | module.exports = postInstallerInfo; 38 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/shell-js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | var shelljs = require("shelljs"); 11 | 12 | module.exports = shelljs; 13 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/wpThemeClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | var wpThemeClient = { 9 | hash: null, 10 | socket: null, 11 | start: function(wsHostProtocol, wsHostname, wsPort) { 12 | var hostProtocol = null; 13 | switch (wsHostProtocol) { 14 | case "ws": 15 | case "wss": 16 | hostProtocol = wsHostProtocol; 17 | break; 18 | default: 19 | console.log(`WPTHEME CLIENT: configHostProtocol is not "ws" or "wss": ` + new String(wsHostProtocol)); 20 | console.error("This is a bug. Please report to: https://github.com/devloco/create-react-wptheme/issues"); 21 | return; 22 | } 23 | 24 | if (wsHostname !== "__from-window__") { 25 | if (typeof wsHostname !== "string" && wsHostname.length <= 0) { 26 | console.log("WPTHEME CLIENT: hostname is not '__from-window__' or a non-empty string: ", wsHostname); 27 | return; 28 | } 29 | } 30 | 31 | var parsedConfigPort = null; 32 | if (wsPort !== "__from-window__") { 33 | parsedConfigPort = parseInt(wsPort, 10); 34 | if (typeof parsedConfigPort !== "number") { 35 | console.log("WPTHEME CLIENT: port is not '__from-window__' or a number: ", wsPort); 36 | return; 37 | } 38 | } 39 | 40 | var hostName = wsHostname === "__from-window__" ? window.location.hostname : wsHostname; 41 | var portNum = wsPort === "__from-window__" ? window.location.port : parsedConfigPort; 42 | var hostURL = hostProtocol + "://" + hostName + ":" + portNum; 43 | 44 | var newlyReloaded = true; 45 | 46 | wpThemeClient.socket = new WebSocket(hostURL); 47 | wpThemeClient.socket.onmessage = function(response) { 48 | if (response && typeof response.data === "string") { 49 | try { 50 | var msg = JSON.parse(response.data); 51 | 52 | if (msg) { 53 | var msgHash = msg && msg.stats && msg.stats.hash; 54 | 55 | if (msg.type === "content-changed") { 56 | if (msg.stats.errors && msg.stats.errors.length > 0) { 57 | msg.type = "errors"; 58 | } 59 | if (msg.stats.warnings && msg.stats.warnings.length > 0) { 60 | msg.type = "warnings"; 61 | } 62 | } 63 | 64 | switch (msg.type) { 65 | case "content-changed": 66 | if (!newlyReloaded || (wpThemeClient.hash === null || (typeof msgHash === "string" && msgHash.length > 0 && msgHash !== wpThemeClient.hash))) { 67 | // Webpack successfully creates a new compile if there are only warnings (unlike errors which do not compile at all). 68 | window.location.reload(); 69 | } 70 | break; 71 | case "errors": 72 | try { 73 | wpThemeErrorOverlay.handleErrors(msg.stats.errors); 74 | } catch (err) { 75 | console.log("'errors' try block error:", err); 76 | console.log("Compile ERRORS", msg); 77 | } 78 | break; 79 | case "hash-check": 80 | if (wpThemeClient.hash === null) { 81 | wpThemeClient.hash = msgHash; 82 | setTimeout(() => { 83 | // In 500ms, let's double-check we have the latest hash... a build on the server may have gotten missed. 84 | if (wpThemeClient.socket && wpThemeClient.socket.send) { 85 | var msgJson = JSON.stringify({ 86 | type: "hash-check" 87 | }); 88 | 89 | wpThemeClient.socket.send(msgJson); 90 | } 91 | }, 500); 92 | } else if (!newlyReloaded && typeof wpThemeClient.hash === "string" && wpThemeClient.hash !== msgHash) { 93 | window.location.reload(); 94 | } 95 | break; 96 | case "warnings": 97 | try { 98 | wpThemeErrorOverlay.handleWarnings(msg.stats.warnings); 99 | if (!newlyReloaded) { 100 | // Webpack successfully creates a new compile if there are only warnings (unlike errors which do not compile at all). 101 | window.location.reload(); 102 | } 103 | } catch (err) { 104 | console.log("'warnings' try block error:", err); 105 | console.log("Compile WARNINGS", err, msg); 106 | } 107 | break; 108 | } 109 | } 110 | } catch (err) { 111 | if (console && typeof console.error === "function") { 112 | console.error(err); 113 | console.log("Raw websocket message:", response); 114 | } 115 | } 116 | 117 | newlyReloaded = false; 118 | wpThemeClient.hash = typeof msgHash === "string" && msgHash.length > 0 ? msgHash : null; 119 | } 120 | }; 121 | 122 | wpThemeClient.socket.onclose = function() { 123 | if (console && typeof console.info === "function") { 124 | switch (wpThemeClient.socket.readyState) { 125 | case wpThemeClient.socket.CLOSED: 126 | case wpThemeClient.socket.CLOSING: 127 | setTimeout(() => { 128 | console.info("It's possible the browser refresh server has disconnected.\nYou can manually refresh the page if necessary."); 129 | }, 1000); 130 | break; 131 | } 132 | } 133 | }; 134 | 135 | wpThemeClient.socket.onopen = function() { 136 | if (console && typeof console.clear === "function") { 137 | //console.clear(); 138 | console.info("The browser refresh server is connected."); 139 | } 140 | }; 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/wpThemeErrorOverlay.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["wpThemeErrorOverlay"] = factory(); 8 | else 9 | root["wpThemeErrorOverlay"] = factory(); 10 | })(window, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 50 | /******/ } 51 | /******/ }; 52 | /******/ 53 | /******/ // define __esModule on exports 54 | /******/ __webpack_require__.r = function(exports) { 55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 57 | /******/ } 58 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 59 | /******/ }; 60 | /******/ 61 | /******/ // create a fake namespace object 62 | /******/ // mode & 1: value is a module id, require it 63 | /******/ // mode & 2: merge all properties of value into the ns 64 | /******/ // mode & 4: return value when already ns object 65 | /******/ // mode & 8|1: behave like require 66 | /******/ __webpack_require__.t = function(value, mode) { 67 | /******/ if(mode & 1) value = __webpack_require__(value); 68 | /******/ if(mode & 8) return value; 69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 70 | /******/ var ns = Object.create(null); 71 | /******/ __webpack_require__.r(ns); 72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 74 | /******/ return ns; 75 | /******/ }; 76 | /******/ 77 | /******/ // getDefaultExport function for compatibility with non-harmony modules 78 | /******/ __webpack_require__.n = function(module) { 79 | /******/ var getter = module && module.__esModule ? 80 | /******/ function getDefault() { return module['default']; } : 81 | /******/ function getModuleExports() { return module; }; 82 | /******/ __webpack_require__.d(getter, 'a', getter); 83 | /******/ return getter; 84 | /******/ }; 85 | /******/ 86 | /******/ // Object.prototype.hasOwnProperty.call 87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 88 | /******/ 89 | /******/ // __webpack_public_path__ 90 | /******/ __webpack_require__.p = ""; 91 | /******/ 92 | /******/ 93 | /******/ // Load entry module and return exports 94 | /******/ return __webpack_require__(__webpack_require__.s = 0); 95 | /******/ }) 96 | /************************************************************************/ 97 | /******/ ([ 98 | /* 0 */ 99 | /***/ (function(module, exports, __webpack_require__) { 100 | 101 | "use strict"; 102 | /* WEBPACK VAR INJECTION */(function(__dirname) {/** 103 | * Copyright (c) 2018-present, https://github.com/devloco 104 | * 105 | * This source code is licensed under the MIT license found in the 106 | * LICENSE file in the root directory of this source tree. 107 | */ 108 | 109 | 110 | const path = __webpack_require__(1); 111 | 112 | module.exports = { 113 | mode: "none", 114 | entry: path.join(__dirname, "src", "index.js"), 115 | watch: false, 116 | watchOptions: { 117 | aggregateTimeout: 600, 118 | ignored: [__dirname, "node_modules", "webpack.config.js", "wpThemeErrorOverlay.js"] 119 | }, 120 | output: { 121 | path: path.join(__dirname, ".."), 122 | filename: "wpThemeErrorOverlay.js", 123 | library: "wpThemeErrorOverlay", 124 | libraryTarget: "umd" 125 | }, 126 | module: { 127 | rules: [ 128 | { 129 | test: /\.js$/, 130 | include: path.join(__dirname, "src"), 131 | use: "babel-loader" 132 | } 133 | ] 134 | } 135 | }; 136 | 137 | /* WEBPACK VAR INJECTION */}.call(this, "/")) 138 | 139 | /***/ }), 140 | /* 1 */ 141 | /***/ (function(module, exports, __webpack_require__) { 142 | 143 | /* WEBPACK VAR INJECTION */(function(process) {// .dirname, .basename, and .extname methods are extracted from Node.js v8.11.1, 144 | // backported and transplited with Babel, with backwards-compat fixes 145 | 146 | // Copyright Joyent, Inc. and other Node contributors. 147 | // 148 | // Permission is hereby granted, free of charge, to any person obtaining a 149 | // copy of this software and associated documentation files (the 150 | // "Software"), to deal in the Software without restriction, including 151 | // without limitation the rights to use, copy, modify, merge, publish, 152 | // distribute, sublicense, and/or sell copies of the Software, and to permit 153 | // persons to whom the Software is furnished to do so, subject to the 154 | // following conditions: 155 | // 156 | // The above copyright notice and this permission notice shall be included 157 | // in all copies or substantial portions of the Software. 158 | // 159 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 160 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 161 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 162 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 163 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 164 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 165 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 166 | 167 | // resolves . and .. elements in a path array with directory names there 168 | // must be no slashes, empty elements, or device names (c:\) in the array 169 | // (so also no leading and trailing slashes - it does not distinguish 170 | // relative and absolute paths) 171 | function normalizeArray(parts, allowAboveRoot) { 172 | // if the path tries to go above the root, `up` ends up > 0 173 | var up = 0; 174 | for (var i = parts.length - 1; i >= 0; i--) { 175 | var last = parts[i]; 176 | if (last === '.') { 177 | parts.splice(i, 1); 178 | } else if (last === '..') { 179 | parts.splice(i, 1); 180 | up++; 181 | } else if (up) { 182 | parts.splice(i, 1); 183 | up--; 184 | } 185 | } 186 | 187 | // if the path is allowed to go above the root, restore leading ..s 188 | if (allowAboveRoot) { 189 | for (; up--; up) { 190 | parts.unshift('..'); 191 | } 192 | } 193 | 194 | return parts; 195 | } 196 | 197 | // path.resolve([from ...], to) 198 | // posix version 199 | exports.resolve = function() { 200 | var resolvedPath = '', 201 | resolvedAbsolute = false; 202 | 203 | for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { 204 | var path = (i >= 0) ? arguments[i] : process.cwd(); 205 | 206 | // Skip empty and invalid entries 207 | if (typeof path !== 'string') { 208 | throw new TypeError('Arguments to path.resolve must be strings'); 209 | } else if (!path) { 210 | continue; 211 | } 212 | 213 | resolvedPath = path + '/' + resolvedPath; 214 | resolvedAbsolute = path.charAt(0) === '/'; 215 | } 216 | 217 | // At this point the path should be resolved to a full absolute path, but 218 | // handle relative paths to be safe (might happen when process.cwd() fails) 219 | 220 | // Normalize the path 221 | resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { 222 | return !!p; 223 | }), !resolvedAbsolute).join('/'); 224 | 225 | return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; 226 | }; 227 | 228 | // path.normalize(path) 229 | // posix version 230 | exports.normalize = function(path) { 231 | var isAbsolute = exports.isAbsolute(path), 232 | trailingSlash = substr(path, -1) === '/'; 233 | 234 | // Normalize the path 235 | path = normalizeArray(filter(path.split('/'), function(p) { 236 | return !!p; 237 | }), !isAbsolute).join('/'); 238 | 239 | if (!path && !isAbsolute) { 240 | path = '.'; 241 | } 242 | if (path && trailingSlash) { 243 | path += '/'; 244 | } 245 | 246 | return (isAbsolute ? '/' : '') + path; 247 | }; 248 | 249 | // posix version 250 | exports.isAbsolute = function(path) { 251 | return path.charAt(0) === '/'; 252 | }; 253 | 254 | // posix version 255 | exports.join = function() { 256 | var paths = Array.prototype.slice.call(arguments, 0); 257 | return exports.normalize(filter(paths, function(p, index) { 258 | if (typeof p !== 'string') { 259 | throw new TypeError('Arguments to path.join must be strings'); 260 | } 261 | return p; 262 | }).join('/')); 263 | }; 264 | 265 | 266 | // path.relative(from, to) 267 | // posix version 268 | exports.relative = function(from, to) { 269 | from = exports.resolve(from).substr(1); 270 | to = exports.resolve(to).substr(1); 271 | 272 | function trim(arr) { 273 | var start = 0; 274 | for (; start < arr.length; start++) { 275 | if (arr[start] !== '') break; 276 | } 277 | 278 | var end = arr.length - 1; 279 | for (; end >= 0; end--) { 280 | if (arr[end] !== '') break; 281 | } 282 | 283 | if (start > end) return []; 284 | return arr.slice(start, end - start + 1); 285 | } 286 | 287 | var fromParts = trim(from.split('/')); 288 | var toParts = trim(to.split('/')); 289 | 290 | var length = Math.min(fromParts.length, toParts.length); 291 | var samePartsLength = length; 292 | for (var i = 0; i < length; i++) { 293 | if (fromParts[i] !== toParts[i]) { 294 | samePartsLength = i; 295 | break; 296 | } 297 | } 298 | 299 | var outputParts = []; 300 | for (var i = samePartsLength; i < fromParts.length; i++) { 301 | outputParts.push('..'); 302 | } 303 | 304 | outputParts = outputParts.concat(toParts.slice(samePartsLength)); 305 | 306 | return outputParts.join('/'); 307 | }; 308 | 309 | exports.sep = '/'; 310 | exports.delimiter = ':'; 311 | 312 | exports.dirname = function (path) { 313 | if (typeof path !== 'string') path = path + ''; 314 | if (path.length === 0) return '.'; 315 | var code = path.charCodeAt(0); 316 | var hasRoot = code === 47 /*/*/; 317 | var end = -1; 318 | var matchedSlash = true; 319 | for (var i = path.length - 1; i >= 1; --i) { 320 | code = path.charCodeAt(i); 321 | if (code === 47 /*/*/) { 322 | if (!matchedSlash) { 323 | end = i; 324 | break; 325 | } 326 | } else { 327 | // We saw the first non-path separator 328 | matchedSlash = false; 329 | } 330 | } 331 | 332 | if (end === -1) return hasRoot ? '/' : '.'; 333 | if (hasRoot && end === 1) { 334 | // return '//'; 335 | // Backwards-compat fix: 336 | return '/'; 337 | } 338 | return path.slice(0, end); 339 | }; 340 | 341 | function basename(path) { 342 | if (typeof path !== 'string') path = path + ''; 343 | 344 | var start = 0; 345 | var end = -1; 346 | var matchedSlash = true; 347 | var i; 348 | 349 | for (i = path.length - 1; i >= 0; --i) { 350 | if (path.charCodeAt(i) === 47 /*/*/) { 351 | // If we reached a path separator that was not part of a set of path 352 | // separators at the end of the string, stop now 353 | if (!matchedSlash) { 354 | start = i + 1; 355 | break; 356 | } 357 | } else if (end === -1) { 358 | // We saw the first non-path separator, mark this as the end of our 359 | // path component 360 | matchedSlash = false; 361 | end = i + 1; 362 | } 363 | } 364 | 365 | if (end === -1) return ''; 366 | return path.slice(start, end); 367 | } 368 | 369 | // Uses a mixed approach for backwards-compatibility, as ext behavior changed 370 | // in new Node.js versions, so only basename() above is backported here 371 | exports.basename = function (path, ext) { 372 | var f = basename(path); 373 | if (ext && f.substr(-1 * ext.length) === ext) { 374 | f = f.substr(0, f.length - ext.length); 375 | } 376 | return f; 377 | }; 378 | 379 | exports.extname = function (path) { 380 | if (typeof path !== 'string') path = path + ''; 381 | var startDot = -1; 382 | var startPart = 0; 383 | var end = -1; 384 | var matchedSlash = true; 385 | // Track the state of characters (if any) we see before our first dot and 386 | // after any path separator we find 387 | var preDotState = 0; 388 | for (var i = path.length - 1; i >= 0; --i) { 389 | var code = path.charCodeAt(i); 390 | if (code === 47 /*/*/) { 391 | // If we reached a path separator that was not part of a set of path 392 | // separators at the end of the string, stop now 393 | if (!matchedSlash) { 394 | startPart = i + 1; 395 | break; 396 | } 397 | continue; 398 | } 399 | if (end === -1) { 400 | // We saw the first non-path separator, mark this as the end of our 401 | // extension 402 | matchedSlash = false; 403 | end = i + 1; 404 | } 405 | if (code === 46 /*.*/) { 406 | // If this is our first dot, mark it as the start of our extension 407 | if (startDot === -1) 408 | startDot = i; 409 | else if (preDotState !== 1) 410 | preDotState = 1; 411 | } else if (startDot !== -1) { 412 | // We saw a non-dot and non-path separator before our dot, so we should 413 | // have a good chance at having a non-empty extension 414 | preDotState = -1; 415 | } 416 | } 417 | 418 | if (startDot === -1 || end === -1 || 419 | // We saw a non-dot character immediately before the dot 420 | preDotState === 0 || 421 | // The (right-most) trimmed path component is exactly '..' 422 | preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { 423 | return ''; 424 | } 425 | return path.slice(startDot, end); 426 | }; 427 | 428 | function filter (xs, f) { 429 | if (xs.filter) return xs.filter(f); 430 | var res = []; 431 | for (var i = 0; i < xs.length; i++) { 432 | if (f(xs[i], i, xs)) res.push(xs[i]); 433 | } 434 | return res; 435 | } 436 | 437 | // String.prototype.substr - negative index don't work in IE8 438 | var substr = 'ab'.substr(-1) === 'b' 439 | ? function (str, start, len) { return str.substr(start, len) } 440 | : function (str, start, len) { 441 | if (start < 0) start = str.length + start; 442 | return str.substr(start, len); 443 | } 444 | ; 445 | 446 | /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(2))) 447 | 448 | /***/ }), 449 | /* 2 */ 450 | /***/ (function(module, exports) { 451 | 452 | // shim for using process in browser 453 | var process = module.exports = {}; 454 | 455 | // cached from whatever global is present so that test runners that stub it 456 | // don't break things. But we need to wrap it in a try catch in case it is 457 | // wrapped in strict mode code which doesn't define any globals. It's inside a 458 | // function because try/catches deoptimize in certain engines. 459 | 460 | var cachedSetTimeout; 461 | var cachedClearTimeout; 462 | 463 | function defaultSetTimout() { 464 | throw new Error('setTimeout has not been defined'); 465 | } 466 | function defaultClearTimeout () { 467 | throw new Error('clearTimeout has not been defined'); 468 | } 469 | (function () { 470 | try { 471 | if (typeof setTimeout === 'function') { 472 | cachedSetTimeout = setTimeout; 473 | } else { 474 | cachedSetTimeout = defaultSetTimout; 475 | } 476 | } catch (e) { 477 | cachedSetTimeout = defaultSetTimout; 478 | } 479 | try { 480 | if (typeof clearTimeout === 'function') { 481 | cachedClearTimeout = clearTimeout; 482 | } else { 483 | cachedClearTimeout = defaultClearTimeout; 484 | } 485 | } catch (e) { 486 | cachedClearTimeout = defaultClearTimeout; 487 | } 488 | } ()) 489 | function runTimeout(fun) { 490 | if (cachedSetTimeout === setTimeout) { 491 | //normal enviroments in sane situations 492 | return setTimeout(fun, 0); 493 | } 494 | // if setTimeout wasn't available but was latter defined 495 | if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { 496 | cachedSetTimeout = setTimeout; 497 | return setTimeout(fun, 0); 498 | } 499 | try { 500 | // when when somebody has screwed with setTimeout but no I.E. maddness 501 | return cachedSetTimeout(fun, 0); 502 | } catch(e){ 503 | try { 504 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 505 | return cachedSetTimeout.call(null, fun, 0); 506 | } catch(e){ 507 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error 508 | return cachedSetTimeout.call(this, fun, 0); 509 | } 510 | } 511 | 512 | 513 | } 514 | function runClearTimeout(marker) { 515 | if (cachedClearTimeout === clearTimeout) { 516 | //normal enviroments in sane situations 517 | return clearTimeout(marker); 518 | } 519 | // if clearTimeout wasn't available but was latter defined 520 | if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { 521 | cachedClearTimeout = clearTimeout; 522 | return clearTimeout(marker); 523 | } 524 | try { 525 | // when when somebody has screwed with setTimeout but no I.E. maddness 526 | return cachedClearTimeout(marker); 527 | } catch (e){ 528 | try { 529 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 530 | return cachedClearTimeout.call(null, marker); 531 | } catch (e){ 532 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. 533 | // Some versions of I.E. have different rules for clearTimeout vs setTimeout 534 | return cachedClearTimeout.call(this, marker); 535 | } 536 | } 537 | 538 | 539 | 540 | } 541 | var queue = []; 542 | var draining = false; 543 | var currentQueue; 544 | var queueIndex = -1; 545 | 546 | function cleanUpNextTick() { 547 | if (!draining || !currentQueue) { 548 | return; 549 | } 550 | draining = false; 551 | if (currentQueue.length) { 552 | queue = currentQueue.concat(queue); 553 | } else { 554 | queueIndex = -1; 555 | } 556 | if (queue.length) { 557 | drainQueue(); 558 | } 559 | } 560 | 561 | function drainQueue() { 562 | if (draining) { 563 | return; 564 | } 565 | var timeout = runTimeout(cleanUpNextTick); 566 | draining = true; 567 | 568 | var len = queue.length; 569 | while(len) { 570 | currentQueue = queue; 571 | queue = []; 572 | while (++queueIndex < len) { 573 | if (currentQueue) { 574 | currentQueue[queueIndex].run(); 575 | } 576 | } 577 | queueIndex = -1; 578 | len = queue.length; 579 | } 580 | currentQueue = null; 581 | draining = false; 582 | runClearTimeout(timeout); 583 | } 584 | 585 | process.nextTick = function (fun) { 586 | var args = new Array(arguments.length - 1); 587 | if (arguments.length > 1) { 588 | for (var i = 1; i < arguments.length; i++) { 589 | args[i - 1] = arguments[i]; 590 | } 591 | } 592 | queue.push(new Item(fun, args)); 593 | if (queue.length === 1 && !draining) { 594 | runTimeout(drainQueue); 595 | } 596 | }; 597 | 598 | // v8 likes predictible objects 599 | function Item(fun, array) { 600 | this.fun = fun; 601 | this.array = array; 602 | } 603 | Item.prototype.run = function () { 604 | this.fun.apply(null, this.array); 605 | }; 606 | process.title = 'browser'; 607 | process.browser = true; 608 | process.env = {}; 609 | process.argv = []; 610 | process.version = ''; // empty string to avoid regexp issues 611 | process.versions = {}; 612 | 613 | function noop() {} 614 | 615 | process.on = noop; 616 | process.addListener = noop; 617 | process.once = noop; 618 | process.off = noop; 619 | process.removeListener = noop; 620 | process.removeAllListeners = noop; 621 | process.emit = noop; 622 | process.prependListener = noop; 623 | process.prependOnceListener = noop; 624 | 625 | process.listeners = function (name) { return [] } 626 | 627 | process.binding = function (name) { 628 | throw new Error('process.binding is not supported'); 629 | }; 630 | 631 | process.cwd = function () { return '/' }; 632 | process.chdir = function (dir) { 633 | throw new Error('process.chdir is not supported'); 634 | }; 635 | process.umask = function() { return 0; }; 636 | 637 | 638 | /***/ }) 639 | /******/ ]); 640 | }); -------------------------------------------------------------------------------- /packages/create-react-wptheme-utils/wpThemeServer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | const url = require("url"); 13 | const https = require("https"); 14 | const WebSocket = require("ws"); 15 | 16 | const _getUserConfig = require("@devloco/create-react-wptheme-utils/getUserConfig"); 17 | const _typeBuildError = "errors"; 18 | const _typeBuildContentChanged = "content-changed"; 19 | const _typeBuildSuccess = "success"; 20 | const _typeBuildWarning = "warnings"; 21 | 22 | const _wsHeartbeat = function() { 23 | this.isAlive = true; 24 | }; 25 | 26 | const _noop = function() {}; 27 | 28 | let _clientInjectString = null; 29 | let _intervalWsClientCheck; 30 | let _webServer; 31 | let _webSocketServer; 32 | 33 | let _lastBuildEvent = null; 34 | let _lastStats = { 35 | hash: undefined 36 | }; 37 | 38 | let _configHost = null; 39 | let _configPort = null; 40 | let _configSslCert = null; 41 | let _configSslKey = null; 42 | 43 | let _webSocketServerProtocol = null; 44 | 45 | let _serverConfig = null; 46 | let _serverPort = null; 47 | 48 | function _sendMessage(buildEvent, stats) { 49 | if (_webSocketServer && stats && stats.hash && stats.hash !== _lastStats.hash) { 50 | // Initial build... there are no clients for the clients.forEach below. 51 | if (_lastStats.hash === undefined) { 52 | _lastBuildEvent = buildEvent; 53 | _lastStats = stats; 54 | } 55 | 56 | _webSocketServer.clients.forEach((ws) => { 57 | _lastBuildEvent = buildEvent; 58 | _lastStats = stats; 59 | 60 | if (ws.isAlive === true) { 61 | let theStats = {}; 62 | if (typeof stats.toJson === "function") { 63 | theStats = stats.toJson("normal"); 64 | } else { 65 | switch (buildEvent) { 66 | case _typeBuildError: 67 | theStats.errors = [stats[0]]; 68 | break; 69 | case _typeBuildWarning: 70 | theStats.warnings = [stats[0]]; 71 | break; 72 | default: 73 | theStats.errors = []; 74 | theStats.warnings = []; 75 | } 76 | } 77 | 78 | const msg = JSON.stringify({ 79 | stats: theStats, 80 | type: buildEvent 81 | }); 82 | 83 | ws.hash = stats.hash; 84 | ws.send(msg); 85 | 86 | if (buildEvent === _typeBuildContentChanged) { 87 | console.log("Browser refresh sent."); 88 | } 89 | } 90 | }); 91 | } 92 | } 93 | 94 | function _webSocketServerSetup() { 95 | _webSocketServer.on("connection", function connection(ws) { 96 | ws.isAlive = true; 97 | ws.on("pong", _wsHeartbeat); 98 | 99 | const msgJson = JSON.stringify({ 100 | type: "hash-check", 101 | hash: _lastStats.hash 102 | }); 103 | ws.send(msgJson); 104 | 105 | ws.on("message", function incoming(message) { 106 | const messageObj = JSON.parse(message); 107 | 108 | switch (messageObj.type) { 109 | case "hash-check": 110 | const msgJson = JSON.stringify({ 111 | type: "hash-check", 112 | stats: { 113 | hash: _lastStats.hash 114 | } 115 | }); 116 | ws.send(msgJson); 117 | break; 118 | case "ssl-client-test": 119 | console.log("websocket message received: %s", messageObj.type); 120 | ws.send("ssl-server-test"); 121 | break; 122 | } 123 | }); 124 | }); 125 | 126 | _webSocketServer.on("close", (code) => { 127 | console.error(`wpThemeServer exited with code ${code}`); 128 | }); 129 | 130 | _intervalWsClientCheck = setInterval(() => { 131 | _webSocketServer.clients.forEach((ws) => { 132 | if (ws.isAlive === false) { 133 | console.log("Browser refresh server: CONNECTION TERMINATED", ws); 134 | return ws.terminate(); 135 | } 136 | 137 | ws.isAlive = false; 138 | ws.ping(_noop); 139 | }); 140 | }, 30000); 141 | } 142 | 143 | function _startNonSslServer() { 144 | _webSocketServer = new WebSocket.Server({ port: _serverPort }); 145 | _webSocketServerSetup(); 146 | } 147 | 148 | function _startSslServer() { 149 | function getWsClientResp(hostName, portNum) { 150 | return ` 151 | 170 | `; 171 | } 172 | 173 | try { 174 | _webServer = new https.createServer( 175 | { 176 | cert: fs.readFileSync(_configSslCert), 177 | key: fs.readFileSync(_configSslKey) 178 | }, 179 | (req, res) => { 180 | res.writeHead(200); 181 | res.end(getWsClientResp(_configHost, _serverPort)); 182 | } 183 | ); 184 | } catch (err) { 185 | console.log("Failed to start SSL server. ERR:", err); 186 | process.exit(1); 187 | } 188 | 189 | _webSocketServer = new WebSocket.Server({ server: _webServer }); 190 | _webSocketServerSetup(); 191 | 192 | _webServer.listen(_serverPort); 193 | } 194 | 195 | const wpThemeServer = { 196 | getClientInjectString: function(mode, token) { 197 | if (_serverConfig.enable !== true || typeof mode !== "string") { 198 | return ""; 199 | } 200 | 201 | if (_clientInjectString) { 202 | return _clientInjectString; 203 | } 204 | 205 | const phpStuff = ``; 206 | const jsTags = [ 207 | "\n", 208 | "\n" 209 | ]; 210 | const jsCall = `\n`; 211 | 212 | let toInject = []; 213 | switch (mode) { 214 | case "afterToken": 215 | // note in this case, we put the token back into the file (i.e. the token is something you want to keep in the file like ""). 216 | toInject = [token, phpStuff, jsTags.join("\n"), jsCall]; 217 | break; 218 | case "beforeToken": 219 | // note in this case, we put the token back into the file (i.e. the token is something you want to keep in the file like ""). 220 | toInject = [phpStuff, jsTags.join("\n"), jsCall, token]; 221 | break; 222 | case "endOfFile": 223 | case "replaceToken": 224 | toInject = [phpStuff, jsTags.join("\n"), jsCall]; 225 | break; 226 | default: 227 | console.log(chalk.magenta(`wpstart::injectWpThemeClient: unknown inject mode: ${mode}.`)); 228 | console.log(`Available inject modes: ${chalk.cyan("disable, afterToken, beforeToken, replaceToken, endOfFile")}`); 229 | process.exit(); 230 | } 231 | 232 | _clientInjectString = toInject.join("\n"); 233 | 234 | return _clientInjectString; 235 | }, 236 | startServer: function(paths) { 237 | try { 238 | _serverConfig = _getUserConfig(paths, process.env.NODE_ENV).wpThemeServer; 239 | } catch (err) { 240 | console.log("unable to get wpThemeServer config from user config. Err:", err); 241 | process.exit(1); 242 | } 243 | 244 | _configHost = _serverConfig && typeof _serverConfig.host === "string" && _serverConfig.host.length > 0 ? _serverConfig.host : "127.0.0.1"; 245 | _configPort = _serverConfig && typeof _serverConfig.port === "number" ? _serverConfig.port : null; 246 | _serverPort = parseInt(process.env.PORT, 10) || _configPort || 8090; 247 | 248 | if (_serverPort > 0) { 249 | _configSslCert = _serverConfig && typeof _serverConfig.sslCert === "string" && _serverConfig.sslCert.length > 0 ? _serverConfig.sslCert : null; 250 | _configSslKey = _serverConfig && typeof _serverConfig.sslKey === "string" && _serverConfig.sslKey.length > 0 ? _serverConfig.sslKey : null; 251 | 252 | if (typeof _configSslCert === "string" && typeof _configSslKey === "string") { 253 | _webSocketServerProtocol = "wss"; 254 | _startSslServer(); 255 | } else { 256 | _webSocketServerProtocol = "ws"; 257 | _startNonSslServer(); 258 | } 259 | 260 | console.log("Browser Refresh Server ready."); 261 | } 262 | }, 263 | update: function(stats, msgType) { 264 | if (stats) { 265 | if (typeof stats.hasErrors === "undefined") { 266 | // This is probably a TypeScript deferred message 267 | switch (msgType) { 268 | case _typeBuildError: 269 | _sendMessage(_typeBuildError, stats); 270 | break; 271 | case _typeBuildWarning: 272 | _sendMessage(_typeBuildWarning, stats); 273 | break; 274 | } 275 | } else { 276 | // Normal Webpack compile message 277 | if (typeof stats.hasErrors === "function " && stats.hasErrors()) { 278 | _sendMessage(_typeBuildError, stats); 279 | } else if (typeof stats.hasWarnings === "function " && stats.hasWarnings()) { 280 | _sendMessage(_typeBuildWarning, stats); 281 | } else { 282 | _sendMessage(_typeBuildContentChanged, stats); 283 | } 284 | } 285 | } 286 | } 287 | }; 288 | 289 | module.exports = wpThemeServer; 290 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/.npmignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /.gitignore 3 | .gitattributes 4 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | shrinkwrap=false 3 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present, https://github.com/devloco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/README.md: -------------------------------------------------------------------------------- 1 | # Create React WP Theme 2 | 3 | ## Up To Date! 4 | 5 | Apr. 15, 2020 6 |
7 | Facebook recently released [v3.4.1](https://github.com/facebook/create-react-app/releases/tag/v3.4.1) of [Create-React-App](https://create-react-app.dev/). 8 | 9 | And now `create-react-wptheme` is up-to-date.
10 | If you have a theme made with an earlier version of `create-react-wptheme` and want to update it to the latest code, [just follow these instructions](#updating-existing-themes). 11 | 12 | --- 13 | 14 | ## Getting Started 15 | 16 | [Michael Soriano](https://github.com/michaelsoriano) is writing a tutorial for creating a theme using React. He uses `create-react-wptheme` for the initial setup. He provides a lot more detail than this `readme` and the screen shots are really helpful. 17 | 18 | Check it out: 19 |
20 | [Let's build a WordPress theme with React: Part 1 (Setup)](http://michaelsoriano.com/wordpress-theme-react-part-1-setup/) 21 | 22 | --- 23 | 24 | See the full [README](https://github.com/devloco/create-react-wptheme) on Github. 25 | 26 | ## Goals 27 | 28 | - Remove WebPackDevServer and use your WordPress dev server instead. 29 | - Also, do not proxy the WordPress server. 30 | - Thus removing CORS as a concern. 31 | - Maintain feature parity(ish) with `create-react-app` 32 | - Touch the original `react-scripts` as little as possible. 33 | - Add new files where possible. 34 | - This will make merges easier. 35 | 36 | ## Acknowledgements 37 | 38 | I'm grateful to the authors of existing related projects for their ideas and collaboration: 39 | 40 | - [create-react-app](https://github.com/facebook/create-react-app) 41 | 42 | The original. 43 | 44 | - [filewatcher-webpack-plugin](https://www.npmjs.com/package/filewatcher-webpack-plugin) 45 | 46 | I used this as an example for writing my own plugin for watching changes to the create-react-app "public" folder. 47 | 48 | ## License 49 | 50 | Create React WP Theme is open source software [licensed as MIT](https://github.com/devloco/create-react-wptheme/blob/master/LICENSE). 51 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/createReactWpTheme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-present, https://github.com/devloco 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | // /!\ DO NOT MODIFY THIS FILE /!\ 10 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | // The only job of create-react-wptheme is to init the repository and then 12 | // forward all the commands to the local version of create-react-wptheme. 13 | // 14 | // If you need to add a new command, please add it to the scripts/ folder. 15 | // 16 | // The only reason to modify this file is to add more warnings and 17 | // troubleshooting information for the `create-react-wptheme` command. 18 | // 19 | // Do not make breaking changes! We absolutely don't want to have to 20 | // tell people to update their global version of create-react-wptheme. 21 | // 22 | // Also be careful with new language features. 23 | // This file must work on Node 6+. 24 | // 25 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | // /!\ DO NOT MODIFY THIS FILE /!\ 27 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | "use strict"; 30 | 31 | const validateProjectName = require("validate-npm-package-name"); 32 | const chalk = require("chalk"); 33 | const commander = require("commander"); 34 | const fs = require("fs-extra"); 35 | const path = require("path"); 36 | const execSync = require("child_process").execSync; 37 | const spawn = require("cross-spawn"); 38 | const dns = require("dns"); 39 | const url = require("url"); 40 | 41 | const packageJson = require("./package.json"); 42 | const _wpThemeVersion = packageJson.version; 43 | const _createReactAppVersion = _wpThemeVersion.split("-wp.")[0]; 44 | 45 | // Check these!!!! 46 | const _reactScriptsWpThemeVersion = "^3.4.1-wp.1"; 47 | const _getScriptsPath = function () { 48 | return scriptsFromNpm(); 49 | }; 50 | 51 | const scriptsFromNpm = function () { 52 | //console.log("SCRIPTS FROM NPM"); 53 | return { 54 | path: `@devloco/react-scripts-wptheme@${_reactScriptsWpThemeVersion}`, 55 | }; 56 | }; 57 | 58 | const scriptsFromGit = function () { 59 | console.log("SCRIPTS FROM GIT"); 60 | const deleteFolderRecursive = (path) => { 61 | if (fs.existsSync(path)) { 62 | fs.readdirSync(path).forEach(function (file) { 63 | let curPath = path + "/" + file; 64 | if (fs.statSync(curPath).isDirectory()) { 65 | // recurse 66 | deleteFolderRecursive(curPath); 67 | } else { 68 | // delete file 69 | fs.unlinkSync(curPath); 70 | } 71 | }); 72 | 73 | fs.rmdirSync(path); 74 | } 75 | }; 76 | 77 | const tempFolderName = "temp"; 78 | fs.ensureDirSync(tempFolderName); 79 | process.chdir(tempFolderName); 80 | const tempPath = process.cwd(); 81 | console.log(chalk.magenta("Cloning @devloco/create-react-app/react-scripts from GitHub...")); 82 | execSync("git clone https://github.com/devloco/create-react-app.git"); 83 | process.chdir(".."); 84 | let scriptsPath = "file:" + path.join(tempPath, "create-react-app", "packages", "react-scripts"); 85 | return { 86 | path: scriptsPath, 87 | callback: function () { 88 | deleteFolderRecursive(tempPath); 89 | }, 90 | }; 91 | }; 92 | 93 | let projectName; 94 | const program = new commander.Command(packageJson.name) 95 | .version(packageJson.version) 96 | .arguments("") 97 | .usage(`${chalk.green("")} [options]`) 98 | .action((name) => { 99 | projectName = name; 100 | }) 101 | .option("--verbose", "force create-react-app to print additional logs (NOTE: create-react-wptheme is always verbose)") 102 | .option("--info", "print environment debug info") 103 | .option("--use-npm", "force downloading packages using npm instead of yarn (if both are installed)") 104 | .option("--use-pnp") 105 | .option("--typescript", "set your theme to use TypeScript") 106 | .allowUnknownOption() 107 | .on("--help", () => { 108 | console.log(` Only ${chalk.green("")} is required.`); 109 | console.log(); 110 | console.log(` If you have any problems, do not hesitate to file an issue:`); 111 | console.log(` ${chalk.cyan("https://github.com/devloco/create-react-wptheme/issues/new")}`); 112 | console.log(); 113 | }) 114 | .parse(process.argv); 115 | 116 | if (program.info) { 117 | console.log(chalk.bold("\nEnvironment Info:")); 118 | return envinfo 119 | .run( 120 | { 121 | System: ["OS", "CPU"], 122 | Binaries: ["Node", "npm", "Yarn"], 123 | Browsers: ["Chrome", "Edge", "Internet Explorer", "Firefox", "Safari"], 124 | npmPackages: ["react", "react-dom", "react-scripts"], 125 | npmGlobalPackages: ["create-react-app"], 126 | }, 127 | { 128 | duplicates: true, 129 | showNotFound: true, 130 | } 131 | ) 132 | .then(console.log); 133 | } 134 | 135 | if (typeof projectName === "undefined") { 136 | console.error("Please specify the project directory:"); 137 | console.log(` ${chalk.cyan(program.name())} ${chalk.green("")}`); 138 | console.log(); 139 | console.log("For example:"); 140 | console.log(` ${chalk.cyan(program.name())} ${chalk.green("my-react-app")}`); 141 | console.log(); 142 | console.log(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`); 143 | process.exit(1); 144 | } 145 | 146 | function printValidationResults(results) { 147 | if (typeof results !== "undefined") { 148 | results.forEach((error) => { 149 | console.error(chalk.red(` * ${error}`)); 150 | }); 151 | } 152 | } 153 | 154 | console.log(program.name() + " version: " + chalk.magenta(_wpThemeVersion)); 155 | console.log("@devloco/react-scripts-wptheme version: " + chalk.magenta(_reactScriptsWpThemeVersion)); 156 | console.log(); 157 | createApp(projectName, program.verbose, program.scriptsVersion, program.useNpm, program.usePnp, program.typescript); 158 | 159 | function createApp(name, verbose, version, useNpm, usePnp, useTypescript, template) { 160 | const root = path.resolve(name); 161 | const appName = path.basename(root); 162 | 163 | checkAppName(appName); 164 | fs.ensureDirSync(name); 165 | 166 | console.log(`Creating a new React WP theme in ${chalk.green(root)}.`); 167 | console.log(`Using Create React App ${chalk.green(_createReactAppVersion)} to scaffold the theme's source code...`); 168 | console.log(); 169 | 170 | let useYarn = useNpm ? false : shouldUseYarn(); 171 | 172 | const originalDirectory = process.cwd(); 173 | process.chdir(root); // change into the newly created folder, then run create-react-app. 174 | 175 | createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript); 176 | } 177 | 178 | function shouldUseYarn() { 179 | try { 180 | execSync("yarnpkg --version", { stdio: "ignore" }); 181 | return true; 182 | } catch (e) { 183 | return false; 184 | } 185 | } 186 | 187 | function createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript) { 188 | const packageToInstall = "create-react-app"; 189 | 190 | if (useTypescript === true) { 191 | template = "wptheme-typescript"; 192 | } 193 | 194 | if (typeof template !== "string" || template.trim().length === 0) { 195 | template = "wptheme"; 196 | } 197 | 198 | return Promise.resolve(packageToInstall) 199 | .then((packageName) => 200 | checkIfOnline(useYarn).then((isOnline) => ({ 201 | isOnline: isOnline, 202 | packageName: packageName, 203 | })) 204 | ) 205 | .then((info) => { 206 | if (!info.isOnline) { 207 | abortCommand(chalk.yellow("You appear to be offline.")); 208 | } 209 | 210 | let createWpThemeReactRoot = "react-src"; 211 | createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp).catch(catchHandler); 212 | }) 213 | .catch(catchHandler); 214 | } 215 | 216 | function createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp) { 217 | return new Promise((resolve, reject) => { 218 | let command = "npx"; 219 | 220 | let args = []; 221 | args.push(`create-react-app@${_createReactAppVersion}`); 222 | args.push(createWpThemeReactRoot); 223 | 224 | if (verbose) { 225 | args.push("--verbose"); 226 | } 227 | 228 | if (!useYarn) { 229 | args.push("--use-npm"); 230 | } 231 | 232 | if (usePnp) { 233 | args.push("--use-pnp"); 234 | } 235 | 236 | args.push("--template"); 237 | args.push(template); 238 | 239 | let scriptsPath = _getScriptsPath(); 240 | args.push("--scripts-version"); 241 | args.push(scriptsPath.path); 242 | 243 | const child = spawn(command, args, { stdio: "inherit" }) 244 | .on("error", function (err) { 245 | console.log(`createReactWpTheme.js ERROR for command: ${command} ${args.join(" ")}`); 246 | throw err; 247 | }) 248 | .on("close", (code) => { 249 | if (code !== 0) { 250 | reject({ 251 | command: `${command} ${args.join(" ")}`, 252 | }); 253 | 254 | return; 255 | } 256 | 257 | scriptsPath && scriptsPath.callback && scriptsPath.callback(); 258 | resolve(); 259 | }); 260 | }).catch(catchHandler); 261 | } 262 | 263 | function checkAppName(appName) { 264 | const validationResult = validateProjectName(appName); 265 | if (!validationResult.validForNewPackages) { 266 | console.error(`Could not create a project called ${chalk.red(`"${appName}"`)} because of npm naming restrictions:`); 267 | 268 | printValidationResults(validationResult.errors); 269 | printValidationResults(validationResult.warnings); 270 | process.exit(1); 271 | } 272 | 273 | // TODO: there should be a single place that holds the dependencies 274 | const dependencies = ["react", "react-dom", "react-scripts", "@devloco/react-scripts-wptheme"].sort(); 275 | if (dependencies.indexOf(appName) >= 0) { 276 | console.error( 277 | chalk.red(`We cannot create a project called ${chalk.green(appName)} because a dependency with the same name exists.\n` + `Due to the way npm works, the following names are not allowed:\n\n`) + 278 | chalk.cyan(dependencies.map((depName) => ` ${depName}`).join("\n")) + 279 | chalk.red("\n\nPlease choose a different project name.") 280 | ); 281 | process.exit(1); 282 | } 283 | } 284 | 285 | function getProxy() { 286 | if (process.env.https_proxy) { 287 | return process.env.https_proxy; 288 | } else { 289 | try { 290 | // Trying to read https-proxy from .npmrc 291 | let httpsProxy = execSync("npm config get https-proxy").toString().trim(); 292 | return httpsProxy !== "null" ? httpsProxy : undefined; 293 | } catch (e) { 294 | return; 295 | } 296 | } 297 | } 298 | 299 | function checkIfOnline(useYarn) { 300 | if (!useYarn) { 301 | // Don't ping the Yarn registry. 302 | // We'll just assume the best case. 303 | return Promise.resolve(true); 304 | } 305 | 306 | return new Promise((resolve) => { 307 | dns.lookup("registry.yarnpkg.com", (err) => { 308 | let proxy; 309 | if (err != null && (proxy = getProxy())) { 310 | // If a proxy is defined, we likely can't resolve external hostnames. 311 | // Try to resolve the proxy name as an indication of a connection. 312 | dns.lookup(url.parse(proxy).hostname, (proxyErr) => { 313 | resolve(proxyErr == null); 314 | }); 315 | } else { 316 | resolve(err == null); 317 | } 318 | }); 319 | }); 320 | } 321 | 322 | function catchHandler(reason) { 323 | console.log(); 324 | console.log(chalk.red("Aborting installation.")); 325 | 326 | if (reason && reason.command) { 327 | console.log(` ${chalk.cyan(reason.command)} has failed.`); 328 | } else { 329 | console.log(chalk.red("Unexpected error."), reason); 330 | console.log(); 331 | console.log("Please report it as a bug here:"); 332 | console.log("https://github.com/devloco/create-react-wptheme/issues"); 333 | } 334 | 335 | console.log(); 336 | console.log("Done."); 337 | process.exit(1); 338 | } 339 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // /!\ DO NOT MODIFY THIS FILE /!\ 5 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | // 7 | // create-react-wptheme is installed globally on people's computers. This means 8 | // that it is extremely difficult to have them upgrade the version and 9 | // because there's only one global version installed, it is very prone to 10 | // breaking changes. 11 | // 12 | // The only job of create-react-wptheme is to init the repository and then 13 | // forward all the commands to the local version of create-react-wptheme. 14 | // 15 | // If you need to add a new command, please add it to the scripts/ folder. 16 | // 17 | // The only reason to modify this file is to add more warnings and 18 | // troubleshooting information for the `create-react-wptheme` command. 19 | // 20 | // Do not make breaking changes! We absolutely don't want to have to 21 | // tell people to update their global version of create-react-wptheme. 22 | // 23 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | // /!\ DO NOT MODIFY THIS FILE /!\ 25 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | 27 | "use strict"; 28 | 29 | var chalk = require("chalk"); 30 | 31 | var currentNodeVersion = process.versions.node; 32 | var semver = currentNodeVersion.split("."); 33 | var major = semver[0]; 34 | 35 | if (major < 8) { 36 | console.error(chalk.red(`You are running Node ${currentNodeVersion}.`)); 37 | console.error(chalk.red("Create React WP Theme requires Node 8 or higher.")); 38 | console.error(chalk.red("Please update your version of Node.")); 39 | process.exit(1); 40 | } 41 | 42 | require("./createReactWpTheme"); 43 | -------------------------------------------------------------------------------- /packages/create-react-wptheme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-wptheme", 3 | "version": "3.4.1-wp.1", 4 | "description": "Create React-enabled WP themes.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/devloco/create-react-wptheme.git" 12 | }, 13 | "keywords": [ 14 | "wordpress", 15 | "create-react-app", 16 | "wordpress-theme", 17 | "wordpress-development", 18 | "wordpress-starter-theme", 19 | "wordpress-api", 20 | "reactjs", 21 | "react", 22 | "react-theme", 23 | "wordpress-server", 24 | "react-tutorial", 25 | "react-wordpress-themes", 26 | "php" 27 | ], 28 | "author": "devloco", 29 | "license": "MIT", 30 | "engines": { 31 | "node": ">=8" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/devloco/create-react-wptheme/issues" 35 | }, 36 | "files": [ 37 | "index.js", 38 | "createReactWpTheme.js" 39 | ], 40 | "homepage": "https://github.com/devloco/create-react-wptheme", 41 | "bin": { 42 | "create-react-wptheme": "./index.js" 43 | }, 44 | "dependencies": { 45 | "chalk": "3.0.0", 46 | "commander": "2.20.0", 47 | "cross-spawn": "6.0.5", 48 | "envinfo": "7.3.1", 49 | "fs-extra": "7.0.1", 50 | "semver": "6.3.0", 51 | "tmp": "0.0.33", 52 | "validate-npm-package-name": "3.0.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /screenshots/console-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/screenshots/console-tab.png -------------------------------------------------------------------------------- /screenshots/network-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/screenshots/network-tab.png -------------------------------------------------------------------------------- /screenshots/view-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devloco/create-react-wptheme/0999330254cc5970901198a925d94a542e365310/screenshots/view-source.png --------------------------------------------------------------------------------