I'm using node/webpack with Wordpress - php

......and I've no idea what the hell I'm doing. 😬
Okay let me explain I'm going back over a Wordpress course I did on Udemy where the now AWOL instructor implemented a automated workflow for us to use whereby anytime we typed PHP or JS the webpage would reload automatically, and it's working, actually it's working too fast for me. This time round for some reason it's driving me bonkers cos when I'm writing my code the page is recompiled before I can even finish typing a complete line of code! The webpage generates a parse error and I have to manually refresh the page when I'm done to clear the error as it doesn't get automatically refreshed due to the error, which sort of defeats the purpose of the automation.
The automated workflow was basically implemented by doing the following:
I've installed 2 files package.json & webpack.config.js into my theme
folder
Then I ran npm run devFast
Then by changing functions.php we got the WP theme to use the Node
generated assets. Node serves up all of the JavaScript and CSS within
one single bundled file, bundled.js
I've no idea where to start in asking for help with this so thought I'd start here. As I say any help with this would be appreciated even if you just signpost me. I'm also using Local by Flywheel to host the site locally and this has been a great tool.
If it helps my webpack file is ...
/*
SUPER IMPORTANT: This config assumes your theme folder is named
exactly 'fictional-university-theme' and that you have a folder
inside it named 'bundled-assets' - If you'd like to adapt this
config to work with your own custom folder structure and names
be sure to adjust the publicPath value on line #116. You do NOT
need to update any of the other publicPath settings in this file,
only the one on line #116.
*/
const currentTask = process.env.npm_lifecycle_event
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const ManifestPlugin = require("webpack-manifest-plugin")
const fse = require("fs-extra")
const postCSSPlugins = [require("postcss-import"), require("postcss-mixins"), require("postcss-simple-vars"), require("postcss-nested"), require("postcss-hexrgba"), require("postcss-color-function"), require("autoprefixer")]
class RunAfterCompile {
apply(compiler) {
compiler.hooks.done.tap("Update functions.php", function () {
// update functions php here
const manifest = fse.readJsonSync("./bundled-assets/manifest.json")
fse.readFile("./functions.php", "utf8", function (err, data) {
if (err) {
console.log(err)
}
const scriptsRegEx = new RegExp("/bundled-assets/scripts.+?'", "g")
const vendorsRegEx = new RegExp("/bundled-assets/vendors.+?'", "g")
const cssRegEx = new RegExp("/bundled-assets/styles.+?'", "g")
let result = data.replace(scriptsRegEx, `/bundled-assets/${manifest["scripts.js"]}'`).replace(vendorsRegEx, `/bundled-assets/${manifest["vendors~scripts.js"]}'`).replace(cssRegEx, `/bundled-assets/${manifest["scripts.css"]}'`)
fse.writeFile("./functions.php", result, "utf8", function (err) {
if (err) return console.log(err)
})
})
})
}
}
let cssConfig = {
test: /\.css$/i,
use: ["css-loader?url=false", { loader: "postcss-loader", options: { plugins: postCSSPlugins } }]
}
let config = {
entry: {
scripts: "./js/scripts.js"
},
plugins: [],
module: {
rules: [
cssConfig,
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-react", ["#babel/preset-env", { targets: { node: "12" } }]]
}
}
}
]
}
}
if (currentTask == "devFast") {
config.devtool = "source-map"
cssConfig.use.unshift("style-loader")
config.output = {
filename: "bundled.js",
publicPath: "http://localhost:3000/"
}
config.devServer = {
before: function (app, server) {
/*
If you want the browser to also perform a traditional refresh
after a save to a JS file you can modify the line directly
below this comment to look like this instead. I'm using this approach
instead of just disabling Hot Module Replacement beacuse this way our
CSS updates can still happen immediately without a page refresh.
If you're using a slower computer and the new bundle is not ready
by the time this is reloading the browser you can always just set the
"hot" property a few lines below this to false instead of true. That
will work on all computers and the only trade off is the browser will
perform a traditional refresh even for CSS changes as well.
*/
// server._watch(["./**/*.php", "./**/*.js"])
server._watch(["./**/*.php", "!./functions.php"])
},
public: "http://localhost:3000",
publicPath: "http://localhost:3000/",
disableHostCheck: true,
contentBase: path.join(__dirname),
contentBasePublicPath: "http://localhost:3000/",
hot: true,
port: 3000,
headers: {
"Access-Control-Allow-Origin": "*"
}
}
config.mode = "development"
}
if (currentTask == "build" || currentTask == "buildWatch") {
cssConfig.use.unshift(MiniCssExtractPlugin.loader)
postCSSPlugins.push(require("cssnano"))
config.output = {
publicPath: "/wp-content/themes/fictional-university-theme/bundled-assets/",
filename: "[name].[chunkhash].js",
chunkFilename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "bundled-assets")
}
config.mode = "production"
config.optimization = {
splitChunks: { chunks: "all" }
}
config.plugins.push(new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: "styles.[chunkhash].css" }), new ManifestPlugin({ publicPath: "" }), new RunAfterCompile())
}
module.exports = config

As already posted:
I should have said I'm using the auto-save feature in VS Code and to save my sanity I've turned this off which is doing the job. But is there another or better way?

Related

How to setup browser-sync with Wordpress and xampp

In my gulpfile.js I have the following code:
const themeName = "test"
const browsersync = require('browser-sync').create();
// Browsersync
function browserSyncServe(cb) {
browsersync.init({
proxy: `localhost/${themeName}/`,
notify: {
styles: {
top: 'auto',
bottom: '0',
},
},
});
cb();
}
function browserSyncReload(cb) {
browsersync.reload();
cb();
}
// Watch Task
function watchTask() {
watch('*.html', browserSyncReload);
watch('*.php', browserSyncReload);
watch(
['src/scss/**/*.scss', 'src/**/*.js'],
series(scssTask, jsTask, browserSyncReload)
);
}
// Default Gulp Task
exports.default = series(
browserSyncServe
);
It seems not so convinient to enter themeName for every single new project and match themeName with Wordpress theme folder name.
Is there any possibility to make it more automatic?
I'm trying to set up browser-sync together with Wordpress website on xampp.
Any help would be much appreciated!
I'm by no means an expert on Browsersync, but I am a WP and xampp user who got it to work perfectly with the following config:
browserSync( {
proxy: "https://localhost/mysite/",
https: {
key: "W:/xampp/htdocs/mkcert/localhost/localhost.key",
cert: "W:/xampp/htdocs/mkcert/localhost/localhost.crt"
}
});
NB: I am using xampp and it's installed on W:/ drive. I am also using mkcert for SSL.
You can learn more here.

How to pass Laravel data into Vue root instance with Laravel Mix Setup

So I am using Laravel 5.5. I have a data coming from my Controller and I want to pass it to my root vue instance not the component.
So for example I have the Dashboard Controller which has a data of "users"
class DashboardController extends Controller {
public function index(){
$user = User::find(1);
return view('index', compact('user'));
}
}
I am using Larave mix on my project setup. So my main js file is the app.js. That "$user" data I need to pass on the root Vue instance. Which is located in app.js
const app = new Vue({
el: '#dashboard',
data: {
// I want all the data from my controller in here.
},
});
If you don't want to use an API call to get data (using axios or else), you could simply try this :
JavaScript::put(['user' => $user ]);
This will, by default, bind your JavaScript variables to a "footer" view. You should load your app.js after this footer view (or modify param bind_js_vars_to_this_view).
In app.js :
data: {
user: user
}
Read more : https://github.com/laracasts/PHP-Vars-To-Js-Transformer
I would make a request to fetch the user's data as has been suggested.
Alternatively, you can add a prop to the dashboard component in index.blade.php and set the user like <dashboard :user="{{ $user }}"></dashboard>. You'll probably want to json_encode or ->toArray() the $user variable.
Then within the dashboard component you can set data values based on the prop.
props: ['user'],
data () {
return {
user: this.user
}
}
I just solved this by placing a reference on the window Object in the <head> of my layout file, and then picking that reference up with a mixin that can be injected into any component.
TLDR SOLUTION
.env
GEODATA_URL="https://geo.some-domain.com"
config/geodata.php
<?php
return [
'url' => env('GEODATA_URL')
];
resources/views/layouts/root.blade.php
<head>
<script>
window.geodataUrl = "{{ config('geodata.url') }}";
</script>
</head>
resources/js/components/mixins/geodataUrl.js
const geodataUrl = {
data() {
return {
geodataUrl: window.geodataUrl,
};
},
};
export default geodataUrl;
usage
<template>
<div>
<a :href="geodataUrl">YOLO</a>
</div>
</template>
<script>
import geodataUrl from '../mixins/geodataUrl';
export default {
name: 'v-foo',
mixins: [geodataUrl],
data() {
return {};
},
computed: {},
methods: {},
};
</script>
END TLDR SOLUTION
If you want, you can use a global mixin instead by adding this to your app.js entrypoint:
Vue.mixin({
data() {
return {
geodataUrl: window.geodataUrl,
};
},
});
I would not recommend using this pattern, however, for any sensitive data because it is sitting on the window Object.
I like this solution because it doesn't use any extra libraries, and the chain of code is very clear. It passes the grep test, in that you can search your code for "window.geodataUrl" and see everything you need to understand how and why the code is working.
That consideration is important if the code may live for a long time and another developer may come across it.
However, JavaScript::put([]) is in my opinion, a decent utility that can be worth having, but in the past I have disliked how it can be extremely difficult to debug if a problem happens, because you cannot see where in the codebase the data comes from.
Imagine you have some Vue code that is consuming window.chartData that came from JavaScript::put([ 'chartData' => $user->chartStuff ]). Depending on the number of references to chartData in your code base, it could take you a very long time to discover which PHP file was responsible for making window.chartData work, especially if you didn't write that code and the next person has no idea JavaScript::put() is being used.
In that case, I recommend putting a comment in the code like:
/* data comes from poop.php via JavaScript::put */
Then the person can search the code for "JavaScript::put" and quickly find it. Keep in mind "the person" could be yourself in 6 months after you forget the implementation details.
It is always a good idea to use Vue component prop declarations like this:
props: {
chartData: {
type: Array,
required: true,
},
},
My point is, if you use JavaScript::put(), then Vue cannot detect as easily if the component fails to receive the data. Vue must assume the data is there on the window Object at the moment in time it refers to it. Your best bet may be to instead create a GET endpoint and make a fetch call in your created/mounted lifecycle method.
I think it is important to have an explicit contract between Laravel and Vue when it comes to getting/setting data.
In the interest of helping you as much as possible by giving you options, here is an example of making a fetch call using ES6 syntax sugar:
routes/web.php
Route::get('/charts/{user}/coolchart', 'UserController#getChart')->name('user.chart');
app/Http/Controllers/UserController.php
public function getChart(Request $request, User $user)
{
// do stuff
$data = $user->chart;
return response()->json([
'chartData' => $data,
]);
}
Anywhere in Vue, especially a created lifecycle method:
created() {
this.handleGetChart();
},
methods: {
async handleGetChart() {
try {
this.state = LOADING;
const { data } = await axios.get(`/charts/${this.user.id}/coolchart`);
if (typeof data !== 'object') {
throw new Error(`Unexpected server response. Expected object, got: ${data}`);
}
this.chartData = data.chartData;
this.state = DATA_LOADED;
} catch (err) {
this.state = DATA_FAILED;
throw new Error(`Problem getting chart data: ${err}`);
}
},
},
That example assumes your Vue component is a Mealy finite state machine, whereby the component can only be in one state at any given time, but it can freely switch between states.
I'd recommend using such states as computed props:
computed: {
isLoading() { return (this.state === LOADING); },
isDataLoaded() { return (this.state === DATA_LOADED); },
isDataFailed() { return (this.state === DATA_FAILED); },
},
With markup such as:
<div v-show="isLoading">Loading...</div>
<v-baller-chart v-if="isDataLoaded" :data="chartData"></v-baller-chart>
<button v-show="isDataFailed" type="button" #click="handleGetChart">TRY AGAIN</button>

POST from vuejs to php script in same directory

I'm trying to post data from vue to php script which exists in the same folder.
when executing npm run dev my vue app start's on port 8080, while my Apache configuration uses port 80. what i want to do is to pass data from my vue app which "lives" in the address http://localhost:8080 to my script which i can access using the address http://localhost:80/project_name .
i tried this app as an example github.com/0x90kh4n ,and while the app works when i simply clone it, when adding it to my "real world" project where i use webpack,babel,npm etc. i cant access it since the app always send requests to localhost:8080 . tried changing the proxy table like this:
proxyTable: {
'/api' : 'http://localhost:80/project_name'
}
but it didn't work, here is my implementation of the code in my (single) vue instance
<script>
import Navigation from './components/partials/Navigation.vue'
export default {
name: 'app',
data () {
return{
name: '',
surname: '',
users: []
}
},
created: function() {
this.fetchData(); // Başlangıçta kayıtları al
},
methods: {
fetchData: function() {
this.$http.get('/api.php')
.then(function(response) {
if (response.body.status == 'ok') {
let users = this.users;
response.body.users.map(function(value, key) {
users.push({name: value.name, surname: value.surname});
});
}
})
.catch(function(error) {
console.log(error);
});
}
},
components: {
Navigation
}
}
in the endpoint there is this file, which i'm not including pieces of code from it here since i believe the problem is my vue part of the story...
Might be worth adding my /config/index.js export:
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api' : 'http://localhost:80/projedt_name'
},
cssSourceMap: false
}

PHP Gulp Livereload

I have a gulp file that I have grabbed from here and modified to this:
var gulp = require('gulp'),
path = require('path'),
del = require('del'),
run = require('run-sequence'),
sass = require('gulp-sass'),
autoprefixer = require('gulp-autoprefixer'),
include = require('gulp-include'),
imagemin = require('gulp-imagemin'),
svgmin = require('gulp-svgmin'),
cache = require('gulp-cache'),
watch = require('gulp-watch'),
livereload = require('gulp-livereload')
//lr = require('tiny-lr'),
//server = lr()
;
var config = {
// Source Config
src : 'src', // Source Directory
src_assets : 'src/assets', // Source Assets Directory
src_fonts : 'src/assets/fonts', // Source Fonts Directory
src_images : 'src/assets/img', // Source Images Directory
src_javascripts : 'src/assets/js', // Source Javascripts Directory
src_stylesheets : 'src/assets/styles', // Source Styles Sheets Directory
src_main_scss : 'src/assets/styles/main.scss', // Source main.scss
src_main_js : 'src/assets/scripts/main.js', // Source main.js
// Destination Config
dist : 'dist', // Destination Directory
dist_assets : 'dist/assets', // Destination Assets Directory
dist_fonts : 'dist/assets/fonts', // Destination Fonts Directory
dist_images : 'dist/assets/img', // Destination Images Directory
dist_javascripts : 'dist/assets/js', // Destination Javascripts Directory
dist_stylesheets : 'dist/assets/css', // Destination Styles Sheets Directory
// Auto Prefixer
autoprefix : 'last 3 version' // Number of version Auto Prefixer to use
};
gulp.task('styles', function () {
return gulp.src(config.src_main_scss)
.pipe(sass({
outputStyle: 'expanded',
precision: 10,
sourceComments: 'none'
}))
.pipe(autoprefixer(config.autoprefix))
.pipe(gulp.dest(config.dist_stylesheets))
.pipe(livereload())
});
gulp.task('scripts', function () {
gulp.src(config.src_main_js)
.pipe(include())
.pipe(gulp.dest(config.dist_javascripts))
.pipe(livereload())
});
gulp.task('php', function() {
return gulp.src([path.join(config.src, '/**/*.php')])
.pipe(gulp.dest(config.dist))
.pipe(livereload())
});
gulp.task('images', function() {
gulp.src(path.join(config.src_images, '/**/*.png'))
.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))
.pipe(gulp.dest(config.dist_images))
});
gulp.task('svg', function() {
gulp.src(path.join(config.src_images, '/**/*.svg'))
.pipe(svgmin())
.pipe(gulp.dest(config.dist_images))
});
gulp.task('clean', function() {
del(path.join(config.dist, '/**/*'))
});
gulp.task('watch', function() {
//server.listen(35729, function (err) {
// if (err) {
// return console.log(err)
// };
gulp.watch(path.join(config.src_stylesheets, '/**/*.scss'), ['styles']);
gulp.watch(path.join(config.src_javascripts, '/**/*.js'), ['scripts']);
gulp.watch(path.join(config.src, '/**/*.php'), ['php']);
//});
});
gulp.task('default', function(cb) {
run('build', ['watch'], cb);
});
gulp.task('build', function (cb) {
run('clean', 'styles', 'scripts', 'php', 'images', 'svg', cb);
});
Now if I run 'gulp' everything works however when I go the my apache host the live reload is no longer working.
I am not that familiar with live reload so please walk me through any solutions.
I have installed live reload plugin and turned it on, still not working.
Am I missing a step with my host?
Alright, I don't know what this Gulpfile promised you in the first place, but I am missing two things here:
Any connection to your up-and-running server.
The place in your HTML file where you insert Livereload. Livereload needs to know where to be injected and just doesn't work automatically out of the box
This can be tricky, especially when you already have a server up and running, and it requires a lot of configuration. One tool which can be integrated with Gulp rather easily and does have a good Livereload setup out of the box is BrowserSync. You can just boot it up and create a proxy to any running server that you have. It will also take care of inserting the LiveReload snippet. I'd strongly suggest you switch to that one, especially if you are pretty new to that topic. I does just all for you :-)
I wrote about BrowserSync here, but here are the changes you would have to do to make it work:
Setup BrowserSync
Add this anywhere to your Gulpfile:
var browserSync = require('browser-sync');
browserSync({
proxy: 'localhost:80',
port: 8080,
open: true,
notify: false
});
(Don't forget to npm install --save-dev browser-sync first). This will create a new server, proxying your MAMP and injecting everything you need for LiveReload. Open your page at localhost:8080 (BrowserSync would do it for yourself), and you are ready to go
Add a reload-Call
Everywhere where you put livereload, change it to
.pipe(browserSync.reload({stream: true}))
Like this for example:
gulp.task('scripts', function () {
return gulp.src(config.src_main_js)
.pipe(include())
.pipe(gulp.dest(config.dist_javascripts))
.pipe(browserSync.reload({stream:true}))
});
This will trigger LiveReload and will refresh your sources. Make sure that you have it everywhere, where you change stuff.
Than you should be able to go. Good luck!
One more thing
Please write a little return statement before every gulp.src. This will let Gulp know that you are working with streams, making it a lot more able to work with streams.

How did they apply Twitter oAuth here?

I have just succeeded in creating oAuth authentication for my twitter application using PHP.
I then saw this site and I am surprised how they open a new window, close that window and then continue the request in the initial window?! Can Someone explain with some Javascript (I am guessing they are using this) how they did this?!
I notice when the second window closes they make two GET requests.
I want to be able to do something like this since my users can write content on my site and I do not want that to get deleted. Is there a better way that isn't so obtrusive? (window popping open). If not, I will use their method as I can not think of anything else.
Thanks all
Here's the part of the JavaScript code that is related to that:
TG.util.oauth = {
win: null,
timer: null,
loginUpdate: function() {
$.getJSON('/-login/check?format=json', TG.util.oauth.loginCallback);
},
loginCallback: function(data) {
if (data && data.loggedin) {
TG.util.login.update(data);
}
},
winCheck: function() {
if (!TG.util.oauth.win || TG.util.oauth.win.closed) {
window.clearInterval(TG.util.oauth.timer);
return TG.util.oauth.loginUpdate();
}
},
loginClick: function() {
TG.util.oauth.win = window.open('/-oauth-twitter/request?gotoafter=1&gotor=oauthtwitter&gotop=action%3Dwindowend',
'OAuthTwitterRequest',
'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes,width=800,height=400');
if (!TG.util.oauth.win) return true;
TG.util.oauth.timer = window.setInterval(TG.util.oauth.winCheck, 300);
return false;
}
};
TG.util.oauth.win = window.open('/-oauth-twitter/request?gotoafter=1&gotor=oauthtwitter&gotop=action%3Dwindowend','OAuthTwitterRequest','menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes,width=800,height=400'); opens the oAuth window, that handles the login
if (!TG.util.oauth.win) return true; returns true if the window isn't open (I guess).
TG.util.oauth.timer = window.setInterval(TG.util.oauth.winCheck, 300); spawns a timer that checks if the login has been done every 300 miliseconds.

Categories