├── .gitignore ├── app ├── css │ └── app.css ├── index.html └── js │ ├── app.js │ └── base.js ├── attendant ├── icons ├── icon.icns ├── title.png └── title.svg ├── main.js ├── mix-manifest.json ├── package.json ├── readme.md ├── shot.png ├── src └── js │ ├── app.js │ ├── components │ ├── ListHeader.vue │ ├── SiteDetails.vue │ ├── SiteList.vue │ ├── SiteListItem.vue │ ├── StatusBar.vue │ └── TitleBar.vue │ └── forge │ └── Forge.js ├── webpack.config.js └── webpack.mix.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.lock 3 | *.log 4 | attendant-darwin-x64 -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | font-family: "Avenir Next"; 4 | font-size: 20px; 5 | cursor: default; 6 | } 7 | html, body{ 8 | padding: 0; 9 | margin: 0; 10 | } 11 | .splitter{ 12 | display: flex; 13 | height: calc(100vh - 89px); 14 | } 15 | .section1{ 16 | min-width: 256px; 17 | border-right: 1px solid #E9E9E9; 18 | } 19 | .section2{ 20 | flex: 1; 21 | } 22 | 23 | .flex-1{ 24 | flex: 1; 25 | } 26 | 27 | .btn{ 28 | border: 1px solid #E9E9E9; 29 | color: #999; 30 | padding: 10px 20px; 31 | background: #fff; 32 | transition: box-shadow .2s ease-in-out; 33 | } 34 | .btn:hover{ 35 | box-shadow: 0px 0px 0px 2px #E9E9E9; 36 | } 37 | 38 | .btn.is-green{ 39 | border: 1px solid #7ED321; 40 | color: #7ED321; 41 | } 42 | .btn.is-green:hover{ 43 | box-shadow: 0px 0px 0px 2px #7ED321; 44 | } 45 | 46 | .btn.is-red{ 47 | border: 1px solid #FB503B; 48 | color: #FB503B; 49 | } 50 | .btn.is-red:hover{ 51 | box-shadow: 0px 0px 0px 2px #FB503B; 52 | } 53 | 54 | 55 | #holder{ 56 | display: none; 57 | position: fixed; 58 | top: 0px; 59 | left: 0px; 60 | right: 0px; 61 | bottom: 0px; 62 | background: #fff; 63 | } 64 | #holder div{ 65 | flex: 1; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | } 70 | #holder div{ 71 | border: 2px dashed #E9E9E9; 72 | font-size: 25px; 73 | color: #555; 74 | } 75 | 76 | .activeate-filedropper #holder{ 77 | display: flex; 78 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attendant 6 | 7 | 8 | 9 |
10 | 11 | 15 |
16 |
17 | 18 | Valet Sites 19 | 20 | 25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 | Drop here to Park 35 |
36 | 39 |
40 | 41 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/js/base.js: -------------------------------------------------------------------------------- 1 | let dirName = __dirname; 2 | let eRequire = require; 3 | let fs = require('fs'); 4 | let shell = require('electron').shell; 5 | let currentWindow = require('electron').remote.getCurrentWindow(); 6 | let exec = require('child_process').exec; 7 | var parse = require('parse-git-config'); 8 | let config, sites, valetVersion; 9 | 10 | console.log(fs); 11 | 12 | let basePath = dirName.split('/'); 13 | basePath = process.env.HOME + "/.valet"; 14 | reloadBase(); 15 | 16 | // fs.watchFile(basePath + '/config.json', reloadBase); 17 | 18 | function reloadBase(){ 19 | valet_version().then((v) => { 20 | console.log("Reloading Base"); 21 | config = JSON.parse(fs.readFileSync(basePath + "/config.json")); 22 | sites = []; 23 | config.paths.forEach((path) => { 24 | fs.readdir(path, (err, files) => { 25 | files.forEach((file) => { 26 | if(file == ".DS_Store") return; 27 | fs.readdir(path + "/" + file, (error, items) => { 28 | let hasGit = items ? items.includes('.git') : false; 29 | if(hasGit){ 30 | hasGit = parse.sync({cwd: path + "/" + file, path: '.git/config'}); 31 | } 32 | 33 | console.log(hasGit); 34 | 35 | let obj = { 36 | site: file + "." + config.domain, 37 | path: path + "/" + file, 38 | git: hasGit 39 | }; 40 | sites.push(obj); 41 | }); 42 | }); 43 | }); 44 | }); 45 | 46 | config.version = v; 47 | 48 | window.app.$emit ? window.app.$emit('reload-base'): ""; 49 | }); 50 | 51 | } 52 | 53 | 54 | function valet_version(){ 55 | return new Promise(function(resolve, reject) { 56 | console.log('Home', process.env.HOME); 57 | exec('/usr/local/bin/valet -V', {cwd: process.env.HOME + "/"}, function(error, stdout, stderr) { 58 | console.log(error); 59 | resolve(stdout); 60 | }); 61 | }); 62 | } 63 | 64 | function valet_which(path){ 65 | return new Promise(function(resolve, reject) { 66 | exec('/usr/local/bin/valet which', {cwd: path}, function(error, stdout, stderr) { 67 | console.log(error); 68 | resolve(stdout.split('[')[1].split(']')[0]); 69 | }); 70 | }); 71 | } 72 | 73 | function valet_restart(){ 74 | return new Promise(function(resolve, reject) { 75 | exec('/usr/local/bin/valet restart', {cwd: process.env.HOME}, function(error, stdout, stderr) { 76 | console.log(error); 77 | resolve(stdout); 78 | }); 79 | }); 80 | } 81 | 82 | function valet_stop(){ 83 | return new Promise(function(resolve, reject) { 84 | exec('/usr/local/bin/valet stop', {cwd: process.env.HOME}, function(error, stdout, stderr) { 85 | console.log(error); 86 | resolve(stdout); 87 | }); 88 | }); 89 | } 90 | 91 | function valet_start(){ 92 | return new Promise(function(resolve, reject) { 93 | exec('/usr/local/bin/valet start', {cwd: process.env.HOME}, function(error, stdout, stderr) { 94 | console.log(error); 95 | resolve(stdout); 96 | }); 97 | }); 98 | } 99 | 100 | function valet_link(path){ 101 | return new Promise(function(resolve, reject) { 102 | exec('/usr/local/bin/valet link', {cwd: path}, function(error, stdout, stderr) { 103 | console.log(error); 104 | resolve(stdout); 105 | }); 106 | }); 107 | } 108 | function valet_unlink(path){ 109 | return new Promise(function(resolve, reject) { 110 | exec('/usr/local/bin/valet unlink', {cwd: path}, function(error, stdout, stderr) { 111 | console.log(error); 112 | resolve(stdout); 113 | }); 114 | }); 115 | } 116 | 117 | function valet_park(path){ 118 | return new Promise(function(resolve, reject) { 119 | exec('/usr/local/bin/valet park', {cwd: path}, function(error, stdout, stderr) { 120 | console.log(error); 121 | resolve(stdout); 122 | }); 123 | }); 124 | } 125 | function valet_forget(path){ 126 | return new Promise(function(resolve, reject) { 127 | exec('/usr/local/bin/valet forget', {cwd: path}, function(error, stdout, stderr) { 128 | console.log(error); 129 | resolve(stdout); 130 | }); 131 | }); 132 | } 133 | 134 | function valet_running(){ 135 | console.log(process.env.HOME + "/.valet/valet.sock"); 136 | return fs.existsSync(process.env.HOME + "/.valet/valet.sock") ? true : false; 137 | } 138 | 139 | function open_sublime(path){ 140 | return new Promise(function(resolve, reject) { 141 | exec('subl .', {cwd: path}, function(error, stdout, stderr) { 142 | console.log(error); 143 | resolve(stdout); 144 | }); 145 | }); 146 | } -------------------------------------------------------------------------------- /attendant: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOURCE="${BASH_SOURCE[0]}" 4 | 5 | sudo open /Applications/Attendant.app -------------------------------------------------------------------------------- /icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phppirate/attendant/a22a6fe5ec89e978233d71d9f5ff18cee8818fe5/icons/icon.icns -------------------------------------------------------------------------------- /icons/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phppirate/attendant/a22a6fe5ec89e978233d71d9f5ff18cee8818fe5/icons/title.png -------------------------------------------------------------------------------- /icons/title.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 14 | Attendant 15 | 16 | 17 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1322 | 1323 | 1324 | 1325 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | let electron = require('electron'); 2 | let {shell, Menu} = electron; 3 | let exec = require('child_process').exec; 4 | let app = electron.app; 5 | let BrowserWindow = electron.BrowserWindow; 6 | let mainWindow; 7 | 8 | function init(){ 9 | createMainWindow(); 10 | } 11 | 12 | function createMainWindow(){ 13 | mainWindow = new BrowserWindow({ 14 | width: 700, 15 | height: 580, 16 | frame: false, 17 | // titleBarStyle: 'hidden' 18 | }); 19 | mainWindow.loadURL('file://' + __dirname + "/app/index.html"); 20 | 21 | let template = [ 22 | { 23 | label: "Attendant", 24 | submenu: [ 25 | { 26 | role: 'about' 27 | }, 28 | { 29 | label: 'Toggle Developer Tools', 30 | accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', 31 | click (item, focusedWindow) { 32 | if (focusedWindow) focusedWindow.webContents.toggleDevTools() 33 | } 34 | }, 35 | { 36 | role: 'reload' 37 | }, 38 | { 39 | role: 'quit' 40 | } 41 | ] 42 | } 43 | ]; 44 | 45 | let menu = Menu.buildFromTemplate(template); 46 | Menu.setApplicationMenu(menu); 47 | 48 | } 49 | 50 | 51 | app.on('ready', init); -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app/js/app.js": "/app/js/app.js" 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Attendant", 3 | "version": "0.0.2", 4 | "description": "Laravel Valet GUI", 5 | "main": "main.js", 6 | "repository": {}, 7 | "author": "Sam Podlogar", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "electron": "^1.4.15", 11 | "laravel-mix": "^0.10.0", 12 | "vue": "^2.1.10" 13 | }, 14 | "scripts": { 15 | "pack": "electron-packager . --icon=icons/icon --overwrite", 16 | "dev": "cross-env NODE_ENV=development webpack --progress --hide-modules", 17 | "watch": "cross-env NODE_ENV=development webpack --watch --progress --hide-modules", 18 | "hot": "cross-env NODE_ENV=development webpack-dev-server --inline --hot", 19 | "production": "cross-env NODE_ENV=production webpack --progress --hide-modules" 20 | }, 21 | "dependencies": { 22 | "parse-git-config": "^1.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## A Laravel Valet GUI 6 | 7 |

8 | 9 |

10 | 11 | ## Requirements 12 | 13 | You need to have valet entirely install already. 14 | 15 | ## Currently Supports 16 | 17 | - Listing Sites 18 | - Showing Valet Running status 19 | - Restarting Valet 20 | - Stoping Valet 21 | - Starting Valet 22 | - Link and Unlink Directories 23 | - Park and Forget Directories 24 | - Opening sites in browser 25 | - Revealing sites in finder 26 | - Opening sites in Sublime Text using the `subl` utility 27 | 28 | ## Installation 29 | 30 | Download the most recent version from the ["Releases"](https://github.com/phppirate/attendant/releases) page and add it to your Applications folder. 31 | 32 | Then run this is your terminal 33 | ``` 34 | ln -s /Applications/Attendant.app/Contents/Resources/app/attendant /usr/local/bin 35 | ``` 36 | Also make sure `/usr/local/bin` is in your $PATH env variable. 37 | 38 | ## Caveat 39 | 40 | Now here is the only issue. When you want to open Attendant you need to open your terminal and type `attendant`. 41 | 42 | That is it! 43 | -------------------------------------------------------------------------------- /shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phppirate/attendant/a22a6fe5ec89e978233d71d9f5ff18cee8818fe5/shot.png -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | let timeout; 4 | 5 | window.app = new Vue({ 6 | el: "#app", 7 | data: { 8 | config: {}, 9 | siteList: [], 10 | activeSite: null, 11 | dropdownOpen: false, 12 | }, 13 | components: { 14 | 'title-bar': require('./components/TitleBar'), 15 | 'status-bar': require('./components/StatusBar'), 16 | 'list-header': require('./components/ListHeader'), 17 | 'site-list': require('./components/SiteList'), 18 | 'site-details': require('./components/SiteDetails'), 19 | }, 20 | methods: { 21 | activateSite(site){ 22 | console.log('Activate'); 23 | this.activeSite = site; 24 | }, 25 | loadBase(){ 26 | this.config = config; 27 | this.siteList = sites; 28 | }, 29 | updateRunning(running){ 30 | this.running = running; 31 | }, 32 | toggleDropdown(){ 33 | this.dropdownOpen = !this.dropdownOpen; 34 | }, 35 | hideDropdown(){ 36 | timeout = setTimeout(() => { 37 | this.dropdownOpen = false; 38 | }, 500); 39 | }, 40 | hoverDropdown(){ 41 | clearTimeout(timeout); 42 | } 43 | }, 44 | mounted(){ 45 | this.$on('reload-base', this.loadBase); 46 | this.$on('update-running', this.updateRunning); 47 | } 48 | }); -------------------------------------------------------------------------------- /src/js/components/ListHeader.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | -------------------------------------------------------------------------------- /src/js/components/SiteDetails.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 127 | 128 | 138 | -------------------------------------------------------------------------------- /src/js/components/SiteList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 32 | 33 | -------------------------------------------------------------------------------- /src/js/components/SiteListItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /src/js/components/StatusBar.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 85 | 86 | -------------------------------------------------------------------------------- /src/js/components/TitleBar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 34 | 35 | -------------------------------------------------------------------------------- /src/js/forge/Forge.js: -------------------------------------------------------------------------------- 1 | class ApiRequest { 2 | static basicRequest(method = "GET"){ 3 | let access_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjYzMWZkNTc3YzQxNTU3NTFmNzNkNmYxZTNjNDg5N2M5N2I4NjgzMzAwMzNkNjY3MmIwMWQwMzY2NzhhYTc3ZDBlMmExNTQ5ZjFiNTA3OWJhIn0.eyJhdWQiOiIxIiwianRpIjoiNjMxZmQ1NzdjNDE1NTc1MWY3M2Q2ZjFlM2M0ODk3Yzk3Yjg2ODMzMDAzM2Q2NjcyYjAxZDAzNjY3OGFhNzdkMGUyYTE1NDlmMWI1MDc5YmEiLCJpYXQiOjE0OTI1Njg2NzAsIm5iZiI6MTQ5MjU2ODY3MCwiZXhwIjoxNTI0MTA0NjcwLCJzdWIiOiIxNjAyMCIsInNjb3BlcyI6W119.MUUx5SbZcy6hmAPCvFaIbmpkCZ6kI1t9YRy-hUab5v64_O0cxO9cCL_187cJWW6_Yldm-LLR-ojlbbJxVMvx66SDUCMmB_ew9zX8_LhjiuGIPqhOVzuivhWp2zqAT9gBjr4QmI-V6XEBrpXXVaF4SvMSWKhtBoSGd3zAS4ciX45XFOwt05QsQ9A-lBuLb3LzOsC8IYoVrPtYW2ni1DkLjLKGGTLoWSVxGCLXm1TgfRkntmDsvzOxmq-5WB6EHAUBJ1UUyfYAm9mK9zxUyq1Rbw1lbga5E1MDywwsI5Bk5df1G6zuexUpVLrrZHgbC19fmqGvhXIuFV4EjBbgl_wiAQbLQ-lA63ufdhJK2lRrmL-ds21-n0H5TGJBhCyKbFy7KY0LvuvXfZ1Te-CFm3wt3qw97oMte8SPOkM1QGmPM6J8kbr9eoOerKd5MasQxBSqzCrHfjpW-1wuIdSlRdGwjnq2KiDJtT69a2qdQym5jOQkOz4kWl41n61FQNpBNSirhN5NtAOoHbpbRh2q50wQykUbLUNdV7VQoYAZoAqrSWpofGNVlwN-EE75A_-o2O1-0t3VLY1jJjLGBHSK5Z8-_BgLo7NQGMwKFBRuGwf_Wg-WZRHpHACOXA2IwunWsvQFQAlOKCr2z9kyJXxTZQd3DGVzSlS-UYMymz3QqF_5Msk"; 4 | let baseOptions = { 5 | method: method, 6 | headers: { 7 | 'Content-Type': 'application/json', 8 | 'Accept': 'application/json'. 9 | 'Authorization': 'Bearer ' + access_token 10 | }, 11 | }; 12 | 13 | } 14 | 15 | 16 | } 17 | 18 | 19 | export default class Forge { 20 | 21 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | let glob = require('glob'); 3 | let webpack = require('webpack'); 4 | let Mix = require('laravel-mix').config; 5 | let webpackPlugins = require('laravel-mix').plugins; 6 | 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Mix Initialization 10 | |-------------------------------------------------------------------------- 11 | | 12 | | As our first step, we'll require the project's Laravel Mix file 13 | | and record the user's requested compilation and build steps. 14 | | Once those steps have been recorded, we may get to work. 15 | | 16 | */ 17 | 18 | Mix.initialize(); 19 | 20 | 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Webpack Context 25 | |-------------------------------------------------------------------------- 26 | | 27 | | This prop will determine the appropriate context, when running Webpack. 28 | | Since you have the option of publishing this webpack.config.js file 29 | | to your project root, we will dynamically set the path for you. 30 | | 31 | */ 32 | 33 | module.exports.context = Mix.Paths.root(); 34 | 35 | 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Webpack Entry 40 | |-------------------------------------------------------------------------- 41 | | 42 | | We'll first specify the entry point for Webpack. By default, we'll 43 | | assume a single bundled file, but you may call Mix.extract() 44 | | to make a separate bundle specifically for vendor libraries. 45 | | 46 | */ 47 | 48 | module.exports.entry = Mix.entry().get(); 49 | 50 | 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Webpack Output 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Webpack naturally requires us to specify our desired output path and 58 | | file name. We'll simply echo what you passed to with Mix.js(). 59 | | Note that, for Mix.version(), we'll properly hash the file. 60 | | 61 | */ 62 | 63 | module.exports.output = Mix.output(); 64 | 65 | 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Rules 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Webpack rules allow us to register any number of loaders and options. 73 | | Out of the box, we'll provide a handful to get you up and running 74 | | as quickly as possible, though feel free to add to this list. 75 | | 76 | */ 77 | 78 | let plugins = []; 79 | 80 | if (Mix.options.extractVueStyles) { 81 | var vueExtractTextPlugin = Mix.vueExtractTextPlugin(); 82 | 83 | plugins.push(vueExtractTextPlugin); 84 | } 85 | 86 | let rules = [ 87 | { 88 | test: /\.vue$/, 89 | loader: 'vue-loader', 90 | options: { 91 | loaders: Mix.options.extractVueStyles ? { 92 | js: 'babel-loader' + Mix.babelConfig(), 93 | scss: vueExtractTextPlugin.extract({ 94 | use: 'css-loader!sass-loader', 95 | fallback: 'vue-style-loader' 96 | }), 97 | sass: vueExtractTextPlugin.extract({ 98 | use: 'css-loader!sass-loader?indentedSyntax', 99 | fallback: 'vue-style-loader' 100 | }), 101 | stylus: vueExtractTextPlugin.extract({ 102 | use: 'css-loader!stylus-loader?paths[]=node_modules', 103 | fallback: 'vue-style-loader' 104 | }), 105 | css: vueExtractTextPlugin.extract({ 106 | use: 'css-loader', 107 | fallback: 'vue-style-loader' 108 | }) 109 | }: { 110 | js: 'babel-loader' + Mix.babelConfig(), 111 | scss: 'vue-style-loader!css-loader!sass-loader', 112 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax', 113 | stylus: 'vue-style-loader!css-loader!stylus-loader?paths[]=node_modules' 114 | }, 115 | 116 | postcss: Mix.options.postCss 117 | } 118 | }, 119 | 120 | { 121 | test: /\.jsx?$/, 122 | exclude: /(node_modules|bower_components)/, 123 | loader: 'babel-loader' + Mix.babelConfig() 124 | }, 125 | 126 | { 127 | test: /\.css$/, 128 | loaders: ['style-loader', 'css-loader'] 129 | }, 130 | 131 | { 132 | test: /\.s[ac]ss$/, 133 | include: /node_modules/, 134 | loaders: ['style-loader', 'css-loader', 'sass-loader'] 135 | }, 136 | 137 | { 138 | test: /\.html$/, 139 | loaders: ['html-loader'] 140 | }, 141 | 142 | { 143 | test: /\.(png|jpe?g|gif)$/, 144 | loaders: [ 145 | { 146 | loader: 'file-loader', 147 | options: { 148 | name: path => { 149 | if (! /node_modules|bower_components/.test(path)) { 150 | return 'images/[name].[ext]?[hash]'; 151 | } 152 | 153 | return 'images/vendor/' + path 154 | .replace(/\\/g, '/') 155 | .replace( 156 | /((.*(node_modules|bower_components))|images|image|img|assets)\//g, '' 157 | ) + '?[hash]'; 158 | }, 159 | publicPath: Mix.options.resourceRoot 160 | } 161 | }, 162 | 'img-loader' 163 | ] 164 | }, 165 | 166 | { 167 | test: /\.(woff2?|ttf|eot|svg|otf)$/, 168 | loader: 'file-loader', 169 | options: { 170 | name: path => { 171 | if (! /node_modules|bower_components/.test(path)) { 172 | return 'fonts/[name].[ext]?[hash]'; 173 | } 174 | 175 | return 'fonts/vendor/' + path 176 | .replace(/\\/g, '/') 177 | .replace( 178 | /((.*(node_modules|bower_components))|fonts|font|assets)\//g, '' 179 | ) + '?[hash]'; 180 | }, 181 | publicPath: Mix.options.resourceRoot 182 | } 183 | }, 184 | 185 | { 186 | test: /\.(cur|ani)$/, 187 | loader: 'file-loader', 188 | options: { 189 | name: '[name].[ext]?[hash]', 190 | publicPath: Mix.options.resourceRoot 191 | } 192 | } 193 | ]; 194 | 195 | if (Mix.preprocessors) { 196 | Mix.preprocessors.forEach(preprocessor => { 197 | rules.push(preprocessor.rules()); 198 | 199 | plugins.push(preprocessor.extractPlugin); 200 | }); 201 | } 202 | 203 | module.exports.module = { rules }; 204 | 205 | 206 | 207 | /* 208 | |-------------------------------------------------------------------------- 209 | | Resolve 210 | |-------------------------------------------------------------------------- 211 | | 212 | | Here, we may set any options/aliases that affect Webpack's resolving 213 | | of modules. To begin, we will provide the necessary Vue alias to 214 | | load the Vue common library. You may delete this, if needed. 215 | | 216 | */ 217 | 218 | module.exports.resolve = { 219 | extensions: ['*', '.js', '.jsx', '.vue'], 220 | 221 | alias: { 222 | 'vue$': 'vue/dist/vue.common.js' 223 | } 224 | }; 225 | 226 | 227 | 228 | /* 229 | |-------------------------------------------------------------------------- 230 | | Stats 231 | |-------------------------------------------------------------------------- 232 | | 233 | | By default, Webpack spits a lot of information out to the terminal, 234 | | each you time you compile. Let's keep things a bit more minimal 235 | | and hide a few of those bits and pieces. Adjust as you wish. 236 | | 237 | */ 238 | 239 | module.exports.stats = { 240 | hash: false, 241 | version: false, 242 | timings: false, 243 | children: false, 244 | errors: false 245 | }; 246 | 247 | process.noDeprecation = true; 248 | 249 | module.exports.performance = { hints: false }; 250 | 251 | 252 | 253 | /* 254 | |-------------------------------------------------------------------------- 255 | | Devtool 256 | |-------------------------------------------------------------------------- 257 | | 258 | | Sourcemaps allow us to access our original source code within the 259 | | browser, even if we're serving a bundled script or stylesheet. 260 | | You may activate sourcemaps, by adding Mix.sourceMaps(). 261 | | 262 | */ 263 | 264 | module.exports.devtool = Mix.options.sourcemaps; 265 | 266 | 267 | 268 | /* 269 | |-------------------------------------------------------------------------- 270 | | Webpack Dev Server Configuration 271 | |-------------------------------------------------------------------------- 272 | | 273 | | If you want to use that flashy hot module replacement feature, then 274 | | we've got you covered. Here, we'll set some basic initial config 275 | | for the Node server. You very likely won't want to edit this. 276 | | 277 | */ 278 | module.exports.devServer = { 279 | historyApiFallback: true, 280 | noInfo: true, 281 | compress: true, 282 | quiet: true 283 | }; 284 | 285 | 286 | 287 | /* 288 | |-------------------------------------------------------------------------- 289 | | Plugins 290 | |-------------------------------------------------------------------------- 291 | | 292 | | Lastly, we'll register a number of plugins to extend and configure 293 | | Webpack. To get you started, we've included a handful of useful 294 | | extensions, for versioning, OS notifications, and much more. 295 | | 296 | */ 297 | 298 | plugins.push( 299 | new webpack.ProvidePlugin(Mix.autoload || {}), 300 | 301 | new webpackPlugins.FriendlyErrorsWebpackPlugin({ clearConsole: Mix.options.clearConsole }), 302 | 303 | new webpackPlugins.StatsWriterPlugin({ 304 | filename: 'mix-manifest.json', 305 | transform: Mix.manifest.transform.bind(Mix.manifest), 306 | }), 307 | 308 | new webpack.LoaderOptionsPlugin({ 309 | minimize: Mix.inProduction, 310 | options: { 311 | postcss: Mix.options.postCss, 312 | context: __dirname, 313 | output: { path: './' } 314 | } 315 | }) 316 | ); 317 | 318 | if (Mix.browserSync) { 319 | plugins.push( 320 | new webpackPlugins.BrowserSyncPlugin( 321 | Object.assign({ 322 | host: 'localhost', 323 | port: 3000, 324 | proxy: 'app.dev', 325 | files: [ 326 | 'app/**/*.php', 327 | 'resources/views/**/*.php', 328 | 'public/js/**/*.js', 329 | 'public/css/**/*.css' 330 | ] 331 | }, Mix.browserSync), 332 | { 333 | reload: false 334 | } 335 | ) 336 | ); 337 | } 338 | 339 | if (Mix.options.notifications) { 340 | plugins.push( 341 | new webpackPlugins.WebpackNotifierPlugin({ 342 | title: 'Laravel Mix', 343 | alwaysNotify: true, 344 | contentImage: Mix.Paths.root('node_modules/laravel-mix/icons/laravel.png') 345 | }) 346 | ); 347 | } 348 | 349 | if (Mix.copy) { 350 | Mix.copy.forEach(copy => { 351 | plugins.push( 352 | new webpackPlugins.CopyWebpackPlugin([copy]) 353 | ); 354 | }); 355 | } 356 | 357 | if (Mix.entry().hasExtractions()) { 358 | plugins.push( 359 | new webpack.optimize.CommonsChunkPlugin({ 360 | names: Mix.entry().getExtractions(), 361 | minChunks: Infinity 362 | }) 363 | ); 364 | } 365 | 366 | if (Mix.options.versioning) { 367 | plugins.push( 368 | new webpack[Mix.inProduction ? 'HashedModuleIdsPlugin': 'NamedModulesPlugin'](), 369 | new webpackPlugins.WebpackChunkHashPlugin() 370 | ); 371 | } 372 | 373 | if (Mix.options.purifyCss) { 374 | let PurifyCSSPlugin = require('purifycss-webpack'); 375 | 376 | // By default, we'll scan all Blade and Vue files in our project. 377 | let paths = glob.sync(Mix.Paths.root('resources/views/**/*.blade.php')).concat( 378 | Mix.entry().scripts.reduce((carry, js) => { 379 | return carry.concat(glob.sync(js.base + '/**/*.vue')); 380 | }, []) 381 | ); 382 | 383 | plugins.push(new PurifyCSSPlugin( 384 | Object.assign({ paths }, Mix.options.purifyCss, { minimize: Mix.inProduction }) 385 | )); 386 | } 387 | 388 | if (Mix.inProduction) { 389 | plugins.push( 390 | new webpack.DefinePlugin({ 391 | 'process.env': { 392 | NODE_ENV: '"production"' 393 | } 394 | }) 395 | ); 396 | 397 | if (Mix.options.uglify) { 398 | plugins.push( 399 | new webpack.optimize.UglifyJsPlugin(Mix.options.uglify) 400 | ); 401 | } 402 | } 403 | 404 | plugins.push( 405 | new webpackPlugins.WebpackOnBuildPlugin( 406 | stats => global.events.fire('build', stats) 407 | ) 408 | ); 409 | 410 | if (! Mix.entry().hasScripts()) { 411 | plugins.push(new webpackPlugins.MockEntryPlugin(Mix.output().path)); 412 | } 413 | 414 | module.exports.plugins = plugins; 415 | 416 | 417 | 418 | /* 419 | |-------------------------------------------------------------------------- 420 | | Mix Finalizing 421 | |-------------------------------------------------------------------------- 422 | | 423 | | Now that we've declared the entirety of our Webpack configuration, the 424 | | final step is to scan for any custom configuration in the Mix file. 425 | | If mix.webpackConfig() is called, we'll merge it in, and build! 426 | | 427 | */ 428 | 429 | if (Mix.webpackConfig) { 430 | module.exports = require('webpack-merge').smart( 431 | module.exports, Mix.webpackConfig 432 | ); 433 | } 434 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for your application, as well as bundling up your JS files. 11 | | 12 | */ 13 | 14 | mix.js('src/js/app.js', 'app/js'); 15 | 16 | // Full API 17 | // mix.js(src, output); 18 | // mix.react(src, output); <-- Identical to mix.js(), but registers React Babel compilation. 19 | // mix.extract(vendorLibs); 20 | // mix.sass(src, output); 21 | // mix.less(src, output); 22 | // mix.stylus(src, output); 23 | // mix.browserSync('my-site.dev'); 24 | // mix.combine(files, destination); 25 | // mix.babel(files, destination); <-- Identical to mix.combine(), but also includes Babel compilation. 26 | // mix.copy(from, to); 27 | // mix.copyDirectory(fromDir, toDir); 28 | // mix.minify(file); 29 | // mix.sourceMaps(); // Enable sourcemaps 30 | // mix.version(); // Enable versioning. 31 | // mix.disableNotifications(); 32 | // mix.setPublicPath('path/to/public'); 33 | // mix.setResourceRoot('prefix/for/resource/locators'); 34 | // mix.autoload({}); <-- Will be passed to Webpack's ProvidePlugin. 35 | // mix.webpackConfig({}); <-- Override webpack.config.js, without editing the file directly. 36 | // mix.then(function () {}) <-- Will be triggered each time Webpack finishes building. 37 | // mix.options({ 38 | // extractVueStyles: false, // Extract .vue component styling to file, rather than inline. 39 | // processCssUrls: true, // Process/optimize relative stylesheet url()'s. Set to false, if you don't want them touched. 40 | // purifyCss: false, // Remove unused CSS selectors. 41 | // uglify: {}, // Uglify-specific options. https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin 42 | // postCss: [] // Post-CSS options: https://github.com/postcss/postcss/blob/master/docs/plugins.md 43 | // }); 44 | --------------------------------------------------------------------------------