## Features
Please visit [browsersync.io](https://browsersync.io) for a full run-down of features
## Requirements
Browsersync works by injecting an asynchronous script tag (``) right after the `` tag
during initial request. In order for this to work properly the `` tag must be present. Alternatively you
can provide a custom rule for the snippet using [snippetOptions](https://www.browsersync.io/docs/options/#option-snippetOptions)
## Upgrading from 1.x to 2.x ?
Providing you haven't accessed any internal properties, everything will just work as
there are no breaking changes to the public API. Internally however, we now use an
immutable data structure for storing/retrieving options. So whereas before you could access urls like this...
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.urls.local);
});
```
... you now access them in the following way:
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.getIn(["urls", "local"]));
});
```
## Install and trouble shooting
[browsersync.io docs](https://browsersync.io)
## Integrations / recipes
[Browsersync recipes](https://github.com/Browsersync/recipes)
## Support
If you've found Browser-sync useful and would like to contribute to its continued development & support, please feel free to send a donation of any size - it would be greatly appreciated!
[Support via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=shakyshane%40gmail%2ecom&lc=US&item_name=browser%2dsync)
## Supported by
Originally supported by [JH](https://www.wearejh.com) - they provided financial support as well as access to a professional designer to help with Branding.
Apache 2
Copyright (c) 2021 Shane Osbourne
================================================
FILE: changelog.js
================================================
const {execSync} = require('child_process');
let [start, end] = process.argv.slice(2);
if (!start || !end) {
console.error('Must provide start and end tags');
console.error(' eg: v1.0 HEAD');
console.error(' eg: v1.0 v2.0');
process.exit(1);
}
const separator = `===END===`;
const res = execSync(`git log -E --format=%H%n%s%b===END=== ${start}..${end}`);
const sep = res.toString().split(separator);
const output = sep
.map((item, i) => {
const [hash, ...body] = getParts(item, i);
const bodyJoined = body.join('\n');
return [hash, bodyJoined];
})
// .filter(([, body]) => )
.map(([hash, bodyJoined]) => {
const hasSection = /^[\w]+: [^ ]/.test(bodyJoined);
if (hasSection) {
const [section, body] = bodyJoined.split(/: /);
return [hash, section, body];
}
return [hash, 'misc', bodyJoined];
})
.reduce((acc, item) => {
const [, section] = item;
if (!acc[section]) {
acc[section] = [item];
} else {
acc[section].push(item);
}
return acc;
}, {});
if (process.argv.indexOf('--json') > -1) {
console.log(JSON.stringify(output, null, 2));
} else {
Object.keys(output)
.map(x => [x, output[x]])
.forEach(([section, items]) => {
const header = `**${section}**`;
console.log(header);
items.forEach(([hash, section, body]) => {
console.log(`- ${body} ${hash}`)
});
console.log('')
});
}
function getParts(item, index) {
const segs = item.split('\n');
if (index === 0) return segs;
return segs.slice(1);
}
================================================
FILE: examples/404.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will redirect all 404 requests to a
* custom 404.html page
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init(
{
files: ["app/css/*.css"],
server: {
baseDir: "app"
}
},
function(err, bs) {
bs.addMiddleware("*", function(req, res) {
res.writeHead(302, {
location: "404.html"
});
res.end("Redirecting!");
});
}
);
================================================
FILE: examples/basic/run.js
================================================
const bs = require("../../packages/browser-sync/dist/index").create();
const path = require("path");
const serverDir = path.join(__dirname, "..", "..", "packages/browser-sync/test/fixtures");
bs.init(
{
server: serverDir,
open: false,
watch: true,
online: false
},
(err, bs) => {
const message = {
kind: "ready",
urls: bs.options.get("urls").toJS(),
cwd: serverDir
};
if (process.send) {
process.send(message);
} else {
console.log(message);
}
}
);
================================================
FILE: examples/callback.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example shows how you can access information about Browsersync when it's running
*
*/
"use strict";
var browserSync = require("browser-sync").create();
var config = {
proxy: "localhost:8000",
files: ["app/css/*.css"]
};
browserSync.init(config, function(err, bs) {
// Full access to Browsersync object here
console.log(bs.getOption("urls"));
});
================================================
FILE: examples/express.js
================================================
/**
*
* Install:
* npm install browser-sync express
*
* Run:
* node
*
* This example will create a server & use the `app` directory as the root
* + use any custom routes you have registered with Express
*
*/
"use strict";
var bs = require("browser-sync").create();
var express = require("express");
var router = express.Router();
var app = express();
/**
* Catch a route like /user/2324
* and send a JSON response
*/
router.get("/user/:id", function(req, res) {
res.send({
name: "shane",
pets: ["cat", "hippo"],
id: req.params.id
});
});
/**
* Register the route with Express
*/
app.use(router);
/**
* Start the Browsersync server and
* load the express app as middleware
*/
bs.init({
server: "./app",
middleware: [app]
});
================================================
FILE: examples/less.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will serve .less files
* and allow them to be injected as a css file would be.
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
server: ["test/fixtures"],
open: false,
watch: true,
injectFileTypes: ["css", "less"],
middleware: [
(req, res, next) => {
if (req.url.indexOf("bootstrap.less") > -1) {
res.setHeader("content-type", "text/css");
}
next();
}
]
});
================================================
FILE: examples/middleware.css.injection.js
================================================
/**
*
* Install:
* npm install browser-sync less
*
* Run:
* node
*
* This example will process less files on the file and auto-inject them into
* all browsers.
*
* Instead of .css, use with the following
* Configuration to enable a super-fast development workflow.
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
/**
* Which files to watch for changes
*/
files: "src/*.less",
/**
* Base directory
*/
server: "app",
/**
* Add .less to the list of files that will cause injection (instead of reload)
*/
injectFileTypes: ["less"],
/**
* Catch all requests, if any are for .less files, recompile on the fly and
* send back a CSS response
*/
middleware: function(req, res, next) {
var parsed = require("url").parse(req.url);
if (parsed.pathname.match(/\.less$/)) {
return less(parsed.pathname).then(function(o) {
res.setHeader("Content-Type", "text/css");
res.end(o.css);
});
}
next();
}
});
function less(src) {
var f = require("fs")
.readFileSync("src/" + src)
.toString();
return require("less").render(f);
}
================================================
FILE: examples/notify-styles.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
*/
"use strict";
var browserSync = require("../").create();
browserSync.init({
open: false, // Stop auto open browser
notify: {
styles: [
"display: none;",
"padding: 6px 15px 3px;",
"position: fixed;",
"font-size: 40px;",
"z-index: 9999;",
"left: 0px;",
"bottom: 0px;",
"color: rgb(74, 74, 74);",
"background-color: rgb(17, 17, 17);",
"color: rgb(229, 229, 229);"
]
},
server: {
baseDir: "test/fixtures"
}
});
================================================
FILE: examples/options.snippetOptions.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example shows how you can place the snippet anywhere.
*/
"use strict";
var path = require("path");
var browserSync = require("../packages/browser-sync").create();
var cwd = path.join(__dirname, "..");
var fixtures_dir = path.join(cwd, "packages/browser-sync/test/fixtures");
browserSync.init({
files: [path.join(fixtures_dir, "css/*.css")],
server: fixtures_dir,
snippetOptions: {
rule: {
match: /<\/head>/i,
fn: function (snippet, match) {
return snippet + match;
},
},
},
});
================================================
FILE: examples/proxy.gzip.js
================================================
/**
*
* Install:
* npm install browser-sync compression
*
* Run:
* node
*
* This example will proxy to your existing vhost
* and serve gzipped responses
*
*/
"use strict";
var browserSync = require("browser-sync").create();
var compression = require("compression");
browserSync.init({
files: ["app/css/*.css"],
proxy: {
target: "http://yourlocal.dev",
middleware: compression()
}
});
================================================
FILE: examples/proxy.headers.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example shows how to specify the proxy headers for each request
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
proxy: {
target: "localhost:8000",
reqHeaders: function(config) {
/**
* These are the default headers as a guide for you.
* You can set whatever you want here.
*/
return {
host: config.urlObj.host,
"accept-encoding": "identity",
agent: false
};
}
}
});
================================================
FILE: examples/proxy.localhost.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will wrap your existing server in a proxy url.
* Use the new Proxy url to access your site.
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
proxy: "localhost:8000"
});
================================================
FILE: examples/proxy.middleware.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a proxy server and run middlewares
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
proxy: {
target: "http://yourlocal.dev",
middleware: function(req, res, next) {
console.log(req.url);
next();
}
},
https: true
});
================================================
FILE: examples/proxy.middleware.multi.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a proxy server and run middlewares
*
*/
"use strict";
var browserSync = require("browser-sync").create();
var fn1 = function(req, res, next) {
console.log(req.url);
next();
};
var fn2 = function(req, res, next) {
console.log(req.headers);
next();
};
browserSync.init({
files: ["app/css/*.css"],
proxy: {
target: "http://yourlocal.dev",
middleware: [fn1, fn2]
},
https: true
});
================================================
FILE: examples/proxy.proxyRes.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will proxy http://www.bbc.co.uk and
* add headers to the response *after* it's returned from
* the server.
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
proxy: {
target: "http://www.bbc.co.uk",
proxyRes: [
function(res) {
res.headers["cache-control"] = "private";
}
]
}
});
================================================
FILE: examples/proxy.rewriteRules.advanced.js
================================================
/**
*
* Install:
* npm install browser-sync serve-static
*
* Run:
* node
*
* This example will
* 1. create a proxy server for a live magento website
* 2. serve static assets from your local `assets` directory
* 3. rewrite HTML on the fly to make the live site use your local assets
*
* eg:
* becomes:
*
* eg:
* becomes:
*
* 4. watch files in the assets directory and reload/inject when anything changes
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
proxy: "http://www.magento-site.com",
files: ["assets"],
middleware: require("serve-static")("."),
rewriteRules: [
{
match: new RegExp("skin/frontend/rwd/(.+?)(?=['\"])", "g"),
replace: "assets/$1"
}
]
});
================================================
FILE: examples/proxy.rewriteRules.simple.js
================================================
/**
*
* Install:
* npm install browser-sync serve-static
*
* Run:
* node
*
* This example will
* 1. create a proxy server for a live magento website
* 2. serve static assets from your local `assets` directory
* 3. rewrite HTML on the fly to make the live site use your local assets/css/core.css file
*
* eg:
* becomes:
*
* 4. watch files in the assets directory and reload/inject when anything changes
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
proxy: "http://www.magento-site.com",
files: ["assets"],
middleware: require("serve-static")("."),
rewriteRules: [
{
match: "skin/frontend/rwd/assets/css/core.min.css",
replace: "assets/css/core.css"
}
]
});
================================================
FILE: examples/proxy.secure.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a proxy server using https
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
proxy: "https://yourlocal.dev"
});
================================================
FILE: examples/proxy.vhost.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will wrap your existing server in a proxy url.
* Use the new Proxy url to access your site.
*
*/
var browserSync = require("browser-sync").create();
browserSync.init({
files: "app/css/*.css",
proxy: "yourvhost.dev"
});
================================================
FILE: examples/server.basedir.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server & use the `app` directory as the root
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app"
}
});
================================================
FILE: examples/server.basedir.mulitple.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server & use the `app` & `dist` directories for serving files
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: ["app", "dist"]
}
});
================================================
FILE: examples/server.default.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server in the cwd.
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: true
});
================================================
FILE: examples/server.gzip.js
================================================
/**
*
* Install:
* npm install browser-sync compression
*
* Run:
* node
*
* This example will create a server with gzip enabled
*
*/
"use strict";
var browserSync = require("browser-sync").create();
var compression = require("compression");
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app",
middleware: compression()
}
});
================================================
FILE: examples/server.http2.js
================================================
/**
*
* Install:
* npm install browser-sync http2
*
* Run:
* node
*
* This example will create a server using http2 using the default information & use the `app` directory as the root
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app"
},
https: true,
httpModule: "http2"
});
================================================
FILE: examples/server.latency.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server & use the `app` directory as the root
* - any requests beginning with /json will have fake latency applied
* - for 3 seconds
*
*/
"use strict";
var browserSync = require("browser-sync").create();
function fakeLatency(req, res, next) {
if (req.url.match(/^\/json/)) {
setTimeout(next, 3000);
} else {
next();
}
}
browserSync.init({
files: ["app/css/*.css"],
server: "app",
middleware: [fakeLatency]
});
================================================
FILE: examples/server.middleware.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server & use the `app` directory as the root
* + use your custom middleware. Note: middleware will be added before
* any Browsersync middlewares
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app",
middleware: function(req, res, next) {
console.log("hi from the middleware");
next();
}
}
});
================================================
FILE: examples/server.middleware.multiple.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server & use the `app` directory as the root
* + use your custom middleware. Note: middleware will be added before
* any Browsersync middlewares
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app",
middleware: [
function(req, res, next) {
console.log("hi from the first middleware");
next();
},
function(req, res, next) {
console.log("hi from the second middleware");
next();
}
]
}
});
================================================
FILE: examples/server.proxy.js
================================================
/**
*
* Install:
* npm install browser-sync express http-proxy-middleware
*
* Run:
* node
*
* This example will create a server in the cwd whilst proxying requests
* to /api to a backend
*
*/
"use strict";
var browserSync = require("browser-sync").create();
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
browserSync.init({
server: ".",
watch: true,
middleware: [app]
});
================================================
FILE: examples/server.secure.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server using https using the default information & use the `app` directory as the root
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app"
},
https: true
});
================================================
FILE: examples/server.secure.pfx.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server using https using a PFX certificate & use the `app` directory as the root
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
files: ["app/css/*.css"],
server: {
baseDir: "app"
},
https: {
pfx: "certs/browsersync.pfx"
}
});
================================================
FILE: examples/server.watch.js
================================================
/**
*
* Install:
* npm install browser-sync
*
* Run:
* node
*
* This example will create a server using https using the default information & use the `app` directory as the root
*
*/
"use strict";
var browserSync = require("browser-sync").create();
browserSync.init({
server: "test/fixtures",
watch: true
});
================================================
FILE: examples/snippet/index.html
================================================
Document
Hello world
================================================
FILE: examples/snippet/run.js
================================================
const bs = require("../../packages/browser-sync/dist/index").create();
bs.init(
{
server: ".",
open: false,
notify: false,
watch: true,
snippetOptions: {
rule: {
match: /<\/head>/i,
fn: function(snippet, match) {
return snippet + match;
}
}
}
},
(err, bs) => {
const message = {
kind: "ready",
urls: bs.options.get("urls").toJS(),
cwd: __dirname
};
if (process.send) {
process.send(message);
} else {
console.log(message);
}
}
);
================================================
FILE: lerna.json
================================================
{
"packages": [
"packages/*"
],
"version": "3.0.4"
}
================================================
FILE: nx.json
================================================
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build"
]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
]
}
}
}
================================================
FILE: package.json
================================================
{
"private": true,
"name": "browser-sync-mono",
"scripts": {
"bootstrap": "lerna bootstrap",
"postinstall": "npm run bootstrap",
"test": "lerna run build && lerna run test --scope browser-sync",
"test:e2e": "echo skipping cypress",
"test:playwright": "playwright test"
},
"devDependencies": {
"lerna": "^6.1.0"
},
"dependencies": {
"@playwright/test": "^1.43.0",
"rxjs": "^7.5.4",
"zod": "^3.22.2"
},
"nx": {}
}
================================================
FILE: packages/browser-sync/.gitignore
================================================
/dist/*
================================================
FILE: packages/browser-sync/.prettierignore
================================================
================================================
FILE: packages/browser-sync/certs/gen.sh
================================================
openssl genrsa -des3 -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
cp server.key server.key.copy
openssl rsa -in server.key.copy -out server.key
rm server.key.copy
================================================
FILE: packages/browser-sync/certs/server.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCir/8eGDIE/jANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJH
QjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDQxMDExNDcyNloXDTI3MDQwODExNDcyNlowRTELMAkG
A1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxzo5Bhk8uupqz0EfdRoCLCUeQi
xVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw/LCC4tEb400F6Ikm6OudVPIB
P+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDLRBnWWluz3iU+rFLMx7YT2Q8k
1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0CmwaV4gfHWGABBTX34WQun7g1Q
YukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPXg4chw9p+YkLLPrr+AOVe/PF1
MJppDT3gKdKMHFo3vMycUf0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAEVmUYRrT
fCQmBDox3evqj4n8dJVStjgdw/7BPkm49SmGtaxsST/eUSGwT7dTvZBLov6BK/OW
+arhHZIvUY/DXlsV4NfCM4TK8KVefrwgd8ZsfQJW73L+FB0IOAY/6s+YKHm/wQGF
ptSOycJvEltsqfIegtYcvvD6c6SkSOvqApaF+Ai10+yiLe20KyOvM3PefZLV7mFE
0zCNyglZ75HftvHHV0wh82T2Et/R+txH+6dTwh065Dd6rrDzljtcAd2HC7B26ERK
dA2zJd9Y4eMz8osacmG/afVuR9rqtFGwdyZ1Kb5xQRzGWlrjvSmAFUx9W9iA4Ilv
3+56a5njSTFYKw==
-----END CERTIFICATE-----
================================================
FILE: packages/browser-sync/certs/server.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIICoTCCAYkCAQAwRTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxz
o5Bhk8uupqz0EfdRoCLCUeQixVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw
/LCC4tEb400F6Ikm6OudVPIBP+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDL
RBnWWluz3iU+rFLMx7YT2Q8k1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0Cm
waV4gfHWGABBTX34WQun7g1QYukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPX
g4chw9p+YkLLPrr+AOVe/PF1MJppDT3gKdKMHFo3vMycUf0CAwEAAaAXMBUGCSqG
SIb3DQEJBzEIDAYxMjM0NTYwDQYJKoZIhvcNAQELBQADggEBABlVUaWK/UUovgPZ
+rqNG8/j6aggSCCye9CkauLB/WqhQFfLl9lWTYdUVmWweNU0SJwDU9lWF/TngipF
RZs6501DkXKxaDT9/3JYg4lRz6zHLy+a77pavJOeN0+cFAPZZuGyxZvYHFYPVSVH
EeJL6bO/nZ/ARgIp0YNkQblDarxq1ARj7YT1Z24D5KgO1kQ55+fCY/wtct8TLGk9
ujvWBbFEBSrJCDjRQeSctlP5qG8yfeJqZwZzo4PQMUwABhNLGUz0z0ceK8W1QnOm
T+nCN5Fni04wO4dAZvYPp6wmU0Gpi2XBihbFl9CT3B5AmJmM8hQVpuQlmXeXT0FO
pOZFpko=
-----END CERTIFICATE REQUEST-----
================================================
FILE: packages/browser-sync/certs/server.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAxEtHZysoHhf/2kzeBHrjWIOxxlZbdqqjvSdQPHOjkGGTy66m
rPQR91GgIsJR5CLFWnccnUeqeKUxbt0RMYaQMcp3ElGOuQGtgeETcrD8sILi0Rvj
TQXoiSbo651U8gE/4K7B80DDvQodLH8K0ittPPQeEMLpc3RrOnf2kMtEGdZaW7Pe
JT6sUszHthPZDyTWdADBxeTMuN5Tu3EC3algZBSC8FARM7lEjtBDQKbBpXiB8dYY
AEFNffhZC6fuDVBi6Ssbf/h9V40vMZbvzsUoK8vTsFPKYlPBddtNM9eDhyHD2n5i
Qss+uv4A5V788XUwmmkNPeAp0owcWje8zJxR/QIDAQABAoIBADbeT/wvnQwkazkL
CXg5HXltfnDRTMmz0wcZiR0MueiuzdA+ZoqrwqXeJCPzK07YxU+PQelY0fbdPh8e
HiM42O+CB5yQPZPLO0O1tWj2vftc6qfG4tdx0lkcDjlmBguLe96DGuWy8cPSousA
K/cpemRyXEEVKopCPYLfa4V3u/Z4be2U/39KNjVkHFhSdSYQl6ferhEfUPwTPi7O
7l1/QUBabqN5FzNc2TeMVhhcJkXtYqF3RxGsaRfT0lK/j2hpbX7Bn2T0CfA/40jY
2OCERqFPfZWx/ShTT52b3fyX/FEua7Nukq/MZdYZou63dDIjCQQyTJSflX6lVojO
SuUoumECgYEA6CSkLiKcRLlTfec3LkjqkWtXR5ibL33g/H1fsZEQKFOyMbIXpUkX
Hybpku8NGeetjKynO3yRirp+NiBHGPn3cHc9WJ5GGG1ew9hRQ9QzyC3Tit15TDbu
J8i50/MaQHZSiUCnPQ/ceIZCNz8STcsEz87o/7utRLJKOvIIAPj+/8kCgYEA2Hd/
v5oUroMRbtzPtMJDMHiGEQyNxEGDNqcuxgXSmiEEqPLfk2qR3yLffzA9UQOg4wkX
/dSXsyomPriKWTvADXu1lNdkPGmW/1tk+onnHu6qgOalva30ZKhtteVjUqxEJEke
mHhNHyIVuj6lExLw9LZhVvzoOi+aj4AD+DRS4pUCgYBEtuveOCJ3eUAMiY9c5PqB
9vsL11FAOouJUXcs8VqOBVA+w4+aPktYzkTfWGFRZLGLbWPHCPVv0gof7Wf+Laef
o7wF6junaWBeqj5LzJlTTLVMaohIFg5iuli/Mzt3D08ZD4kxWuuQxXT+M24wlsKi
3IU9hYkhR4EPd6sE1q9seQKBgEpQRBAgModywbJgpgH1SyHBzqzdtXGx1/0USg97
gkCdoz7pGm4+gNOs4jOE+Rft+fbXcWAX8vh0OOsBaaWWyKkYVk9B3syKp2cFFlaY
rzrETs6v4CiNJsDDvd5bYMzKDR6z54gKjNdqWTE2Pm+c6hHo5uP5MTSAkTxAg5xb
QjU9AoGAaYPXlm3IKVO12FgNg/ffduooi0PKa1yRNJGnhpQKNvBQXs8eV+CQ83aK
kQHUExuJDrOfsC2iwF/2ZywXhEfbhL7ar0aw5zrhV+r7qvYFWxu/YoLoNVMDByw5
wAN0oIbsGWYmtIIti8+b9IcacTbAZ79ctlTLb1HCyPMosHxDkv8=
-----END RSA PRIVATE KEY-----
================================================
FILE: packages/browser-sync/cli-options/opts.init.json
================================================
{}
================================================
FILE: packages/browser-sync/cli-options/opts.recipe.json
================================================
{
"output": {
"alias": "o",
"desc": "Specify an output directory"
}
}
================================================
FILE: packages/browser-sync/cli-options/opts.reload.json
================================================
{
"files": {
"desc": "File paths to reload",
"type": "array",
"alias": "f"
},
"port": {
"alias": "p",
"type": "number",
"desc": "Target a running instance by port number"
},
"url": {
"alias": "u",
"desc": "Provide the full the url to the running Browsersync instance"
}
}
================================================
FILE: packages/browser-sync/cli-options/opts.start.json
================================================
{
"server": {
"alias": "s",
"desc": "Run a Local server (uses your cwd as the web root)"
},
"cwd": {
"type": "string",
"desc": "Working directory"
},
"json": {
"type": "boolean",
"desc": "If true, certain logs will output as json only"
},
"serveStatic": {
"type": "array",
"alias": "ss",
"desc": "Directories to serve static files from"
},
"port": {
"type": "number",
"desc": "Specify a port to use"
},
"proxy": {
"alias": "p",
"desc": "Proxy an existing server",
"example": "$0 shane is cool"
},
"ws": {
"type": "boolean",
"desc": "Proxy mode only - enable websocket proxying"
},
"browser": {
"type": "array",
"alias": "b",
"desc": "Choose which browser should be auto-opened"
},
"watch": {
"type": "boolean",
"alias": "w",
"desc": "Watch files"
},
"ignore": {
"type": "array",
"desc": "Ignore patterns for file watchers"
},
"files": {
"type": "array",
"alias": "f",
"desc": "File paths to watch"
},
"index": {
"type": "string",
"desc": "Specify which file should be used as the index page"
},
"plugins": {
"type": "array",
"desc": "Load Browsersync plugins"
},
"extensions": {
"type": "array",
"desc": "Specify file extension fallbacks"
},
"startPath": {
"type": "string",
"desc": "Specify the start path for the opened browser"
},
"single": {
"type": "boolean",
"desc": "If true, the connect-history-api-fallback middleware will be added"
},
"https": {
"desc": "Enable SSL for local development"
},
"directory": {
"type": "boolean",
"desc": "Show a directory listing for the server"
},
"tunnel": {
"desc": "Use a public URL"
},
"open": {
"type": "string",
"desc": "Choose which URL is auto-opened (local, external or tunnel), or provide a url"
},
"cors": {
"type": "boolean",
"desc": "Add Access Control headers to every request"
},
"config": {
"type": "string",
"alias": "c",
"desc": "Specify a path to a configuration file"
},
"host": {
"desc": "Specify a hostname to use"
},
"listen": {
"desc": "Specify a hostname bind to (this will prevent binding to all interfaces)"
},
"logLevel": {
"desc": "Set the logger output level (silent, info or debug)"
},
"reload-delay": {
"type": "number",
"desc": "Time in milliseconds to delay the reload event following file changes"
},
"reload-debounce": {
"type": "number",
"desc": "Restrict the frequency in which browser:reload events can be emitted to connected clients"
},
"ui-port": {
"type": "number",
"desc": "Specify a port for the UI to use"
},
"watchEvents": {
"type": "array",
"desc": "Specify which file events to respond to"
},
"no-notify": {
"desc": "Disable the notify element in browsers"
},
"no-open": {
"desc": "Don't open a new browser window"
},
"no-snippet": {
"desc": "Disable the snippet injection"
},
"no-online": {
"desc": "Force offline usage"
},
"no-ui": {
"desc": "Don't start the user interface"
},
"no-ghost-mode": {
"desc": "Disable Ghost Mode"
},
"no-inject-changes": {
"desc": "Reload on every file change"
},
"no-reload-on-restart": {
"desc": "Don't auto-reload all browsers following a restart"
}
}
================================================
FILE: packages/browser-sync/lib/args.js
================================================
"use strict";
/**
* The purpose of this function is
* to handle back-backwards compatibility
* @param {Object} args
* @returns {{config: {}, cb: *}}
*/
module.exports = function(args) {
var config = {};
var cb;
switch (args.length) {
case 1:
if (isFilesArg(args[0])) {
config.files = args[0];
} else if (typeof args[0] === "function") {
cb = args[0];
} else {
config = args[0];
}
break;
case 2:
// if second is a function, first MUST be config
if (typeof args[1] === "function") {
config = args[0] || {};
cb = args[1];
} else {
if (args[1] === null || args[1] === undefined) {
config = args[0];
} else {
// finally, second arg could be a plain object for config
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
}
}
break;
case 3:
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
cb = args[2];
}
return {
config: config,
cb: cb
};
};
/**
* Files args were only ever strings or arrays
* @param arg
* @returns {*|boolean}
*/
function isFilesArg(arg) {
return Array.isArray(arg) || typeof arg === "string";
}
================================================
FILE: packages/browser-sync/lib/async-tasks.js
================================================
var async = require("./async");
module.exports = [
{
step: "Finding an empty port",
fn: async.getEmptyPort
},
{
step: "Getting an extra port for Proxy",
fn: async.getExtraPortForProxy
},
{
step: "Checking online status",
fn: async.getOnlineStatus
},
{
step: "Resolve user plugins from options",
fn: async.resolveInlineUserPlugins
},
{
step: "Set Urls and other options that rely on port/online status",
fn: async.setOptions
},
{
step: "Setting Internal Events",
fn: async.setInternalEvents
},
{
step: "Setting file watchers",
fn: async.setFileWatchers
},
{
step: "Merging middlewares from core + plugins",
fn: async.mergeMiddlewares
},
{
step: "Starting the Server",
fn: async.startServer
},
{
step: "Starting the HTTPS Tunnel",
fn: async.startTunnel
},
{
step: "Starting the web-socket server",
fn: async.startSockets
},
{
step: "Starting the UI",
fn: async.startUi
},
{
step: "Merge UI settings",
fn: async.mergeUiSettings
},
{
step: "Init user plugins",
fn: async.initUserPlugins
}
];
================================================
FILE: packages/browser-sync/lib/async.js
================================================
"use strict";
var _ = require("./lodash.custom");
var Immutable = require("immutable");
var utils = require("./utils");
var pluginUtils = require("./plugins");
var connectUtils = require("./connect-utils");
var chalk = require("chalk");
module.exports = {
/**
* BrowserSync needs at least 1 free port.
* It will check the one provided in config
* and keep incrementing until an available one is found.
* @param {BrowserSync} bs
* @param {Function} done
*/
getEmptyPort: function(bs, done) {
utils.getPorts(bs.options, function(err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
bs.debug("Found a free port: %s", chalk.magenta(port));
done(null, {
options: {
port: port
}
});
});
},
/**
* If the running mode is proxy, we'll use a separate port
* for the Browsersync web-socket server. This is to eliminate any issues
* with trying to proxy web sockets through to the users server.
* @param bs
* @param done
*/
getExtraPortForProxy: function(bs, done) {
/**
* An extra port is not needed in snippet/server mode
*/
if (bs.options.get("mode") !== "proxy") {
return done();
}
/**
* Web socket support is disabled by default
*/
if (!bs.options.getIn(["proxy", "ws"])) {
return done();
}
/**
* Use 1 higher than server port by default...
*/
var socketPort = bs.options.get("port") + 1;
/**
* Or use the user-defined socket.port option instead
*/
if (bs.options.hasIn(["socket", "port"])) {
socketPort = bs.options.getIn(["socket", "port"]);
}
utils.getPort(
bs.options.get("listen", "localhost"),
socketPort,
null,
function(err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
done(null, {
optionsIn: [
{
path: ["socket", "port"],
value: port
}
]
});
}
);
},
/**
* Some features require an internet connection.
* If the user did not provide either `true` or `false`
* for the online option, we will attempt to resolve www.google.com
* as a way of determining network connectivity
* @param {BrowserSync} bs
* @param {Function} done
*/
getOnlineStatus: function(bs, done) {
if (
_.isUndefined(bs.options.get("online")) &&
_.isUndefined(process.env.TESTING)
) {
require("dns").resolve("www.google.com", function(err) {
var online = false;
if (err) {
bs.debug(
"Could not resolve www.google.com, setting %s",
chalk.magenta("online: false")
);
} else {
bs.debug(
"Resolved www.google.com, setting %s",
chalk.magenta("online: true")
);
online = true;
}
done(null, {
options: {
online: online
}
});
});
} else {
done();
}
},
/**
* Try to load plugins that were given in options
* @param {BrowserSync} bs
* @param {Function} done
*/
resolveInlineUserPlugins: function(bs, done) {
var plugins = bs.options
.get("plugins")
.map(pluginUtils.resolvePlugin)
.map(pluginUtils.requirePlugin);
plugins.forEach(function(plugin) {
if (plugin.get("errors").size) {
return logPluginError(plugin);
}
var jsPlugin = plugin.toJS();
jsPlugin.options = jsPlugin.options || {};
jsPlugin.options.moduleName = jsPlugin.moduleName;
bs.registerPlugin(jsPlugin.module, jsPlugin.options);
});
function logPluginError(plugin) {
utils.fail(true, plugin.getIn(["errors", 0]), bs.cb);
}
done();
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
setOptions: function(bs, done) {
done(null, {
options: {
urls: utils.getUrlOptions(bs.options),
snippet: connectUtils.enabled(bs.options)
? connectUtils.scriptTags(bs.options)
: false,
scriptPaths: Immutable.fromJS(
connectUtils.clientScript(bs.options, true)
),
files: bs.pluginManager.hook(
"files:watch",
bs.options.get("files"),
bs.pluginManager.pluginOptions
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setInternalEvents: function(bs, done) {
require("./internal-events")(bs);
done();
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setFileWatchers: function(bs, done) {
done(null, {
instance: {
watchers: bs.pluginManager.get("file:watcher")(bs)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeMiddlewares: function(bs, done) {
done(null, {
options: {
middleware: bs.pluginManager.hook(
"server:middleware",
bs.options.get("middleware")
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startServer: function(bs, done) {
var server = bs.pluginManager.get("server")(bs);
done(null, {
instance: {
server: server.server,
app: server.app
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startTunnel: function(bs, done) {
if (bs.options.get("tunnel") && bs.options.get("online")) {
var localTunnel = require("./tunnel");
localTunnel(bs, function(err, tunnel) {
if (err) {
if (err.code === "MODULE_NOT_FOUND") {
return utils.fail(true, err, bs.cb);
}
return done(err);
} else {
return done(null, {
optionsIn: [
{
path: ["urls", "tunnel"],
value: tunnel.url
}
],
instance: {
tunnel: tunnel
}
});
}
});
} else {
done();
}
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startSockets: function(bs, done) {
var clientEvents = bs.pluginManager.hook(
"client:events",
bs.options.get("clientEvents").toJS()
);
// Start the socket, needs an existing server.
var io = bs.pluginManager.get("socket")(bs.server, clientEvents, bs);
done(null, {
instance: {
io: io
},
options: {
clientEvents: Immutable.fromJS(clientEvents)
}
});
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
startUi: function(bs, done) {
var PLUGIN_NAME = "UI";
var userPlugins = bs.getUserPlugins();
var ui = bs.pluginManager.get(PLUGIN_NAME);
var uiOpts = bs.options.get("ui");
if (!uiOpts || uiOpts.get("enabled") === false) {
return done();
}
// if user provided a UI, use it instead
if (
userPlugins.some(function(item) {
return item.name === PLUGIN_NAME;
})
) {
uiOpts = bs.options
.get("ui")
.mergeDeep(
Immutable.fromJS(
bs.pluginManager.pluginOptions[PLUGIN_NAME]
)
);
}
/**
* Append the 'listen' option
*/
const opts = uiOpts.update(uiOpts => {
const listen = bs.options.get("listen");
if (listen) {
return uiOpts.set("listen", listen);
}
return uiOpts;
});
return ui(opts.toJS(), bs, function(err, ui) {
if (err) {
return done(err);
}
done(null, {
instance: {
ui: ui
}
});
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeUiSettings: function(bs, done) {
if (!bs.ui) {
return done();
}
done(null, {
options: {
urls: bs.options.get("urls").merge(bs.ui.options.get("urls"))
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
initUserPlugins: function(bs, done) {
bs.pluginManager.initUserPlugins(bs);
done(null, {
options: {
userPlugins: bs.getUserPlugins()
}
});
}
};
================================================
FILE: packages/browser-sync/lib/bin.ts
================================================
#!/usr/bin/env node
const startOpts = require("../cli-options/opts.start.json");
const reloadOpts = require("../cli-options/opts.reload.json");
const recipeOpts = require("../cli-options/opts.recipe.json");
const chalk = require("chalk");
const pkg = require("../package.json");
import * as utils from "./utils";
import { resolve } from "path";
import { existsSync } from "fs";
import { logger } from "./logger";
import { printErrors } from "./cli/cli-options";
export enum BsErrorLevels {
Fatal = "Fatal"
}
export enum BsErrorTypes {
PathNotFound = "PathNotFound",
HostAndListenIncompatible = "HostAndListenIncompatible"
}
export type BsErrors = BsError[];
export interface BsError {
type: BsErrorTypes;
level: BsErrorLevels;
errors: BsErrorItem[];
}
export interface BsErrorItem {
error: Error;
meta?(...args): string[];
}
/**
* Handle cli input
*/
if (!module.parent) {
runFromCli();
}
function freshYargs() {
return require("yargs")(process.argv.slice(2));
}
function runFromCli() {
const yargs = freshYargs()
.command("start", "Start the server")
.command("init", "Create a configuration file")
.command("reload", "Send a reload event over HTTP protocol")
.command("recipe", "Generate the files for a recipe")
.version(pkg.version)
.help(false)
.epilogue(
[
"For help running a certain command, type --help",
" $0 start --help",
"",
"You can run a static server by providing a path(s) directly",
" $0 app/src app/tmp",
"",
"If the directory contains a 'index.html' file, you can omit any input",
" $0",
"",
"You can run the proxy in this manner too",
" $0 https://example.com",
"",
"To run a proxy, whilst also serving static files",
" $0 https://example.com htdocs/themes/example"
].join("\n")
);
const argv = yargs.argv;
const input = argv._;
const command = input[0];
const valid = ["start", "init", "reload", "recipe"];
if (valid.indexOf(command) > -1) {
return handleIncoming(command, freshYargs());
}
if (input.length) {
return handleNoCommand(argv, input, freshYargs());
}
if (existsSync("index.html")) {
return handleNoCommand(argv, ["."], freshYargs());
}
yargs.showHelp();
}
/**
* Feature: If no command was specified, try to do the 'right thing'
*
* If paths were given, start the server
* eg: browser-sync app/code app/design
* is equal to: browser-sync start --server app/code app/design
*
* eg: browser-sync http://example.com
* is equal to: browser-sync start --proxy http://example.com
*
* eg: browser-sync http://example.com themes/example
* is equal to: browser-sync start --proxy http://example.com --ss themes/example
*
* @param argv
* @param input
* @returns {any}
*/
function handleNoCommand(argv, input, yargs) {
const processed = processStart(yargs);
const paths = input.map(path => {
const resolved = resolve(path);
const isUrl = /^https?:\/\//.test(path);
return {
isUrl,
userInput: path,
resolved,
errors: isUrl ? [] : pathErrors(path, resolved)
};
});
const withErrors = paths.filter(item => item.errors.length);
const withoutErrors = paths.filter(item => item.errors.length === 0);
if (withErrors.length) {
withErrors.forEach(item => {
logger.unprefixed("error", printErrors(item.errors));
});
return process.exit(1);
}
const serveStaticPaths = withoutErrors
.filter(item => item.isUrl === false)
.map(item => item.resolved);
const urls = withoutErrors
.filter(item => item.isUrl === true)
.map(item => item.userInput);
/**
* If a URL was given, switch to proxy mode and use
* any other paths as serveStatic options
*/
if (urls.length) {
const proxy = urls[0];
const config = {
...processed,
proxy,
serveStatic: serveStaticPaths
};
return handleCli({ cli: { flags: config, input: ["start"] } });
}
/**
* if NO urls were given switch directly to server mode
* @type {{server: {baseDir: any}}}
*/
const config = {
...processed,
server: { baseDir: serveStaticPaths }
};
return handleCli({ cli: { flags: config, input: ["start"] } });
}
/**
* @param {{cli: object, [whitelist]: array, [cb]: function}} opts
* @returns {*}
*/
function handleCli(opts) {
opts.cb = opts.cb || utils.defaultCallback;
const m = require(`./cli/command.${opts.cli.input[0]}`);
if (m.default) {
return m.default(opts);
}
return m(opts);
}
export default handleCli;
function processStart(yargs) {
return yargs
.usage("Usage: $0 start [options]")
.options(startOpts)
.example("$0 start -s app", "- Use the App directory to serve files")
.example("$0 start -p www.bbc.co.uk", "- Proxy an existing website")
.default("cwd", () => process.cwd())
.argv;
}
/**
* @param {string} command
* @param {object} yargs
* @param cwd
*/
function handleIncoming(command, yargs) {
let out;
if (command === "start") {
out = processStart(yargs);
}
if (command === "init") {
out = yargs
.usage("Usage: $0 init")
.example("$0 init")
.default("cwd", () => process.cwd())
.help().argv;
}
if (command === "reload") {
out = yargs
.usage("Usage: $0 reload")
.options(reloadOpts)
.example("$0 reload")
.example("$0 reload --port 4000")
.default("cwd", () => process.cwd())
.argv;
}
if (command === "recipe") {
out = yargs
.usage("Usage: $0 recipe ")
.option(recipeOpts)
.example("$0 recipe ls", "list the recipes")
.example("$0 recipe gulp.sass", "use the gulp.sass recipe")
.default("cwd", () => process.cwd())
.argv;
}
if (out.help) {
return yargs.showHelp();
}
handleCli({ cli: { flags: out, input: out._ } });
}
function pathErrors(input, resolved): BsErrors {
if (!existsSync(resolved)) {
return [
{
type: BsErrorTypes.PathNotFound,
level: BsErrorLevels.Fatal,
errors: [
{
error: new Error(`Path not found: ${input}`),
meta() {
return [
`Your Input: ${chalk.yellow(input)}`,
`CWD: ${chalk.yellow(process.cwd())}`,
`Resolved to: ${chalk.yellow(resolved)}`
];
}
}
]
}
];
}
return [];
}
================================================
FILE: packages/browser-sync/lib/browser-sync.js
================================================
"use strict";
var hooks = require("./hooks");
var asyncTasks = require("./async-tasks");
var config = require("./config");
var connectUtils = require("./connect-utils");
var utils = require("./utils");
var logger = require("./logger");
var chalk = require("chalk");
var eachSeries = utils.eachSeries;
var _ = require("./lodash.custom");
var EE = require("easy-extender");
/**
* Required internal plugins.
* Any of these can be overridden by deliberately
* causing a name-clash.
*/
var defaultPlugins = {
logger: logger,
socket: require("./sockets"),
"file:watcher": require("./file-watcher"),
server: require("./server"),
tunnel: require("./tunnel"),
"client:script": require("browser-sync-client"),
UI: require("browser-sync-ui")
};
/**
* @constructor
*/
var BrowserSync = function(emitter) {
var bs = this;
bs.cwd = process.cwd();
bs.active = false;
bs.paused = false;
bs.config = config;
bs.utils = utils;
bs.events = bs.emitter = emitter;
bs._userPlugins = [];
bs._reloadQueue = [];
bs._cleanupTasks = [];
bs._browserReload = false;
// Plugin management
bs.pluginManager = new EE(defaultPlugins, hooks);
};
/**
* Call a user-options provided callback
* @param name
*/
BrowserSync.prototype.callback = function(name) {
var bs = this;
var cb = bs.options.getIn(["callbacks", name]);
if (_.isFunction(cb)) {
cb.apply(bs.publicInstance, _.toArray(arguments).slice(1));
}
};
/**
* @param {Map} options
* @param {Function} cb
* @returns {BrowserSync}
*/
BrowserSync.prototype.init = function(options, cb) {
/**
* Safer access to `this`
* @type {BrowserSync}
*/
var bs = this;
/**
* Set user-provided callback, or assign a noop
* @type {Function}
*/
bs.cb = cb || utils.defaultCallback;
/**
* Verify provided config.
* Some options are not compatible and will cause us to
* end the process.
*/
if (!utils.verifyConfig(options, bs.cb)) {
return;
}
/**
* Save a reference to the original options
* @type {Map}
* @private
*/
bs._options = options;
/**
* Set additional options that depend on what the
* user may of provided
* @type {Map}
*/
bs.options = options;
/**
* Kick off default plugins.
*/
bs.pluginManager.init();
/**
* Create a base logger & debugger.
*/
bs.logger = bs.pluginManager.get("logger")(bs.events, bs);
bs.debugger = bs.logger.clone({ useLevelPrefixes: true });
bs.debug = bs.debugger.debug;
/**
* Run each setup task in sequence
*/
eachSeries(asyncTasks, taskRunner(bs), tasksComplete(bs));
return this;
};
/**
* Run 1 setup task.
* Each task is a pure function.
* They can return options or instance properties to set,
* but they cannot set them directly.
* @param {BrowserSync} bs
* @returns {Function}
*/
function taskRunner(bs) {
return function(item, cb) {
bs.debug("-> %s", chalk.yellow("Starting Step: " + item.step));
/**
* Execute the current task.
*/
item.fn(bs, executeTask);
function executeTask(err, out) {
/**
* Exit early if any task returned an error.
*/
if (err) {
return cb(err);
}
/**
* Act on return values (such as options to be set,
* or instance properties to be set
*/
if (out) {
handleOut(bs, out);
}
bs.debug("+ %s", chalk.green("Step Complete: " + item.step));
cb();
}
};
}
/**
* @param bs
* @param out
*/
function handleOut(bs, out) {
/**
* Set a single/many option.
*/
if (out.options) {
setOptions(bs, out.options);
}
/**
* Any options returned that require path access?
*/
if (out.optionsIn) {
out.optionsIn.forEach(function(item) {
bs.setOptionIn(item.path, item.value);
});
}
/**
* Any instance properties returned?
*/
if (out.instance) {
Object.keys(out.instance).forEach(function(key) {
bs[key] = out.instance[key];
});
}
}
/**
* Update the options Map
* @param bs
* @param options
*/
function setOptions(bs, options) {
/**
* If multiple options were set, act on the immutable map
* in an efficient way
*/
if (Object.keys(options).length > 1) {
bs.setMany(function(item) {
Object.keys(options).forEach(function(key) {
item.set(key, options[key]);
return item;
});
});
} else {
Object.keys(options).forEach(function(key) {
bs.setOption(key, options[key]);
});
}
}
/**
* At this point, ALL async tasks have completed
* @param {BrowserSync} bs
* @returns {Function}
*/
function tasksComplete(bs) {
return function(err) {
if (err) {
bs.logger.setOnce("useLevelPrefixes", true).error(err.message);
}
/**
* Set active flag
*/
bs.active = true;
/**
* @deprecated
*/
bs.events.emit("init", bs);
/**
* This is no-longer needed as the Callback now only resolves
* when everything (including slow things, like the tunnel) is ready.
* It's here purely for backwards compatibility.
* @deprecated
*/
bs.events.emit("service:running", {
options: bs.options,
baseDir: bs.options.getIn(["server", "baseDir"]),
type: bs.options.get("mode"),
port: bs.options.get("port"),
url: bs.options.getIn(["urls", "local"]),
urls: bs.options.get("urls").toJS(),
tunnel: bs.options.getIn(["urls", "tunnel"])
});
/**
* Call any option-provided callbacks
*/
bs.callback("ready", null, bs);
/**
* Finally, call the user-provided callback given as last arg
*/
bs.cb(null, bs);
};
}
/**
* @param module
* @param opts
* @param cb
*/
BrowserSync.prototype.registerPlugin = function(module, opts, cb) {
var bs = this;
bs.pluginManager.registerPlugin(module, opts, cb);
if (module["plugin:name"]) {
bs._userPlugins.push(module);
}
};
/**
* Get a plugin by name
* @param name
*/
BrowserSync.prototype.getUserPlugin = function(name) {
var bs = this;
var items = bs.getUserPlugins(function(item) {
return item["plugin:name"] === name;
});
if (items && items.length) {
return items[0];
}
return false;
};
/**
* @param {Function} [filter]
*/
BrowserSync.prototype.getUserPlugins = function(filter) {
var bs = this;
filter =
filter ||
function() {
return true;
};
/**
* Transform Plugins option
*/
bs.userPlugins = bs._userPlugins.filter(filter).map(function(plugin) {
return {
name: plugin["plugin:name"],
active: plugin._enabled,
opts: bs.pluginManager.pluginOptions[plugin["plugin:name"]]
};
});
return bs.userPlugins;
};
/**
* Get middleware
* @returns {*}
*/
BrowserSync.prototype.getMiddleware = function(type) {
var types = {
connector: connectUtils.socketConnector(this.options)
};
if (type in types) {
return function(req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(types[type]);
};
}
};
/**
* Shortcut for pushing a file-serving middleware
* onto the stack
* @param {String} path
* @param {{type: string, content: string}} props
*/
var _serveFileCount = 0;
BrowserSync.prototype.serveFile = function(path, props) {
var bs = this;
var mode = bs.options.get("mode");
var entry = {
handle: function(req, res) {
res.setHeader("Content-Type", props.type);
res.end(props.content);
},
id: "Browsersync - " + _serveFileCount++,
route: path
};
bs._addMiddlewareToStack(entry);
};
/**
* Add middlewares on the fly
*/
BrowserSync.prototype._addMiddlewareToStack = function(entry) {
var bs = this;
/**
* additional middlewares are always appended -1,
* this is to allow the proxy middlewares to remain,
* and the directory index to remain in serveStatic/snippet modes
*/
bs.app.stack.splice(bs.app.stack.length - 1, 0, entry);
};
var _addMiddlewareCount = 0;
BrowserSync.prototype.addMiddleware = function(route, handle, opts) {
var bs = this;
if (!bs.app) {
return;
}
opts = opts || {};
if (!opts.id) {
opts.id = "bs-mw-" + _addMiddlewareCount++;
}
if (route === "*") {
route = "";
}
var entry = {
id: opts.id,
route: route,
handle: handle
};
if (opts.override) {
entry.override = true;
}
bs.options = bs.options.update("middleware", function(mw) {
if (bs.options.get("mode") === "proxy") {
return mw.insert(mw.size - 1, entry);
}
return mw.concat(entry);
});
bs.resetMiddlewareStack();
};
/**
* Remove middlewares on the fly
* @param {String} id
* @returns {Server}
*/
BrowserSync.prototype.removeMiddleware = function(id) {
var bs = this;
if (!bs.app) {
return;
}
bs.options = bs.options.update("middleware", function(mw) {
return mw.filter(function(mw) {
return mw.id !== id;
});
});
bs.resetMiddlewareStack();
};
/**
* Middleware for socket connection (external usage)
* @param opts
* @returns {*}
*/
BrowserSync.prototype.getSocketConnector = function(opts) {
var bs = this;
return function(req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(bs.getExternalSocketConnector(opts));
};
};
/**
* Socket connector as a string
* @param {Object} opts
* @returns {*}
*/
BrowserSync.prototype.getExternalSocketConnector = function(opts) {
var bs = this;
return connectUtils.socketConnector(
bs.options.withMutations(function(item) {
item.set("socket", item.get("socket").merge(opts));
if (!bs.options.getIn(["proxy", "ws"])) {
item.set("mode", "snippet");
}
})
);
};
/**
* Callback helper
* @param name
*/
BrowserSync.prototype.getOption = function(name) {
this.debug("Getting option: {magenta:%s", name);
return this.options.get(name);
};
/**
* Callback helper
* @param path
*/
BrowserSync.prototype.getOptionIn = function(path) {
this.debug("Getting option via path: {magenta:%s", path);
return this.options.getIn(path);
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getOptions = function() {
return this.options;
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getLogger = logger.getLogger;
/**
* @param {String} name
* @param {*} value
* @returns {BrowserSync.options|*}
*/
BrowserSync.prototype.setOption = function(name, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", name, value.toString());
bs.options = bs.options.set(name, value);
if (!opts.silent) {
bs.events.emit("options:set", {
path: name,
value: value,
options: bs.options
});
}
return this.options;
};
/**
* @param path
* @param value
* @param opts
* @returns {Map|*|BrowserSync.options}
*/
BrowserSync.prototype.setOptionIn = function(path, value, opts) {
var bs = this;
opts = opts || {};
bs.debug(
"Setting Option: {cyan:%s} - {magenta:%s",
path.join("."),
value.toString()
);
bs.options = bs.options.setIn(path, value);
if (!opts.silent) {
bs.events.emit("options:set", {
path: path,
value: value,
options: bs.options
});
}
return bs.options;
};
/**
* Set multiple options with mutations
* @param fn
* @param opts
* @returns {Map|*}
*/
BrowserSync.prototype.setMany = function(fn, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting multiple Options");
bs.options = bs.options.withMutations(fn);
if (!opts.silent) {
bs.events.emit("options:set", { options: bs.options.toJS() });
}
return this.options;
};
BrowserSync.prototype.addRewriteRule = function(rule) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function(rules) {
return rules.concat(rule);
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.removeRewriteRule = function(id) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function(rules) {
return rules.filter(function(rule) {
return rule.id !== id;
});
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.setRewriteRules = function(rules) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function(_) {
return rules;
});
bs.resetMiddlewareStack();
};
/**
* Add a new rewrite rule to the stack
* @param {Object} rule
*/
BrowserSync.prototype.resetMiddlewareStack = function() {
var bs = this;
var middlewares = require("./server/utils").getMiddlewares(bs, bs.options);
bs.app.stack = middlewares;
};
/**
* @param fn
*/
BrowserSync.prototype.registerCleanupTask = function(fn) {
this._cleanupTasks.push(fn);
};
/**
* Instance Cleanup
*/
BrowserSync.prototype.cleanup = function(cb) {
var bs = this;
if (!bs.active) {
return;
}
// Remove all event listeners
if (bs.events) {
bs.debug("Removing event listeners...");
bs.events.removeAllListeners();
}
// Close any core file watchers
if (bs.watchers) {
Object.keys(bs.watchers).forEach(function(key) {
bs.watchers[key].watchers.forEach(function(watcher) {
watcher.close();
});
});
}
// Run any additional clean up tasks
bs._cleanupTasks.forEach(function(fn) {
if (_.isFunction(fn)) {
fn(bs);
}
});
// Reset the flag
bs.debug("Setting {magenta:active: false");
bs.active = false;
bs.paused = false;
bs.pluginManager.plugins = {};
bs.pluginManager.pluginOptions = {};
bs.pluginManager.defaultPlugins = defaultPlugins;
bs._userPlugins = [];
bs.userPlugins = [];
bs._reloadTimer = undefined;
bs._reloadQueue = [];
bs._cleanupTasks = [];
if (_.isFunction(cb)) {
cb(null, bs);
}
};
module.exports = BrowserSync;
================================================
FILE: packages/browser-sync/lib/cli/cli-info.js
================================================
"use strict";
var config = require("../config");
var logger = require("../logger").logger;
var fs = require("fs");
var _ = require("../lodash.custom");
var path = require("path");
var chalk = require("chalk");
var info = {
/**
* Version info
* @param {Object} pjson
* @returns {String}
*/
getVersion: function(pjson) {
console.log(pjson.version);
return pjson.version;
},
/**
* Retrieve the config file
* @returns {*}
* @private
* @param filePath
*/
getConfigFile: function(filePath) {
return require(path.resolve(filePath));
},
/**
* Generate an example Config file.
*/
makeConfig: function(cwd, cb) {
var opts = require(path.join(__dirname, "..", config.configFile));
var userOpts = {};
var ignore = ["excludedFileTypes", "injectFileTypes", "snippetOptions"];
Object.keys(opts).forEach(function(key) {
if (!_.includes(ignore, key)) {
userOpts[key] = opts[key];
}
});
var file = fs.readFileSync(config.template, "utf8");
file = file.replace("//OPTS", JSON.stringify(userOpts, null, 4));
fs.writeFile(path.resolve(cwd, config.userFile), file, function() {
logger.info("Config file created %s", chalk.magenta(config.userFile));
logger.info(
"To use it, in the same directory run: " +
chalk.cyan("browser-sync start --config bs-config.js")
);
cb();
});
}
};
module.exports = info;
================================================
FILE: packages/browser-sync/lib/cli/cli-options.ts
================================================
import { Map, List, fromJS } from "immutable";
import { addToFilesOption } from "./transforms/addToFilesOption";
import { addDefaultIgnorePatterns } from "./transforms/addDefaultIgnorePatterns";
import { copyCLIIgnoreToWatchOptions } from "./transforms/copyCLIIgnoreToWatchOptions";
import { handleExtensionsOption } from "./transforms/handleExtensionsOption";
import { handleFilesOption } from "./transforms/handleFilesOption";
import { handleGhostModeOption } from "./transforms/handleGhostModeOption";
import { handlePortsOption } from "./transforms/handlePortsOption";
import { handleProxyOption } from "./transforms/handleProxyOption";
import { handleServerOption } from "./transforms/handleServerOption";
import { appendServerIndexOption } from "./transforms/appendServerIndexOption";
import { appendServerDirectoryOption } from "./transforms/appendServerDirectoryOption";
import { addCwdToWatchOptions } from "./transforms/addCwdToWatchOptions";
import {
setMode,
setScheme,
setStartPath,
setProxyWs,
setServerOpts,
liftExtensionsOptionFromCli,
setNamespace,
fixSnippetIgnorePaths,
fixSnippetIncludePaths,
fixRewriteRules,
setMiddleware,
setOpen,
setUiPort
} from "../options";
import { BsErrors } from "../bin";
import { handleHostOption } from "./transforms/handleHostOption";
const _ = require("../lodash.custom");
const defaultConfig = require("../default-config");
const immDefs = fromJS(defaultConfig);
/**
* @param {Object} input
* @returns {Map}
*/
export type BsTempOptions = Map;
export type TransformResult = [BsTempOptions, BsErrors];
export type TransformFn = (subject: BsTempOptions) => TransformResult;
export function merge(input) {
const merged = immDefs.mergeDeep(input);
const transforms: TransformFn[] = [
addToFilesOption,
addCwdToWatchOptions,
addDefaultIgnorePatterns,
copyCLIIgnoreToWatchOptions,
handleServerOption,
appendServerIndexOption,
appendServerDirectoryOption,
handleProxyOption,
handlePortsOption,
handleHostOption,
handleGhostModeOption,
handleFilesOption,
handleExtensionsOption,
setMode,
setScheme,
setStartPath,
setProxyWs,
setServerOpts,
liftExtensionsOptionFromCli,
setNamespace,
fixSnippetIgnorePaths,
fixSnippetIncludePaths,
fixRewriteRules,
setMiddleware,
setOpen,
setUiPort
];
const output = transforms.reduce(
(acc: TransformResult, item: TransformFn) => {
const [current, currentErrors] = acc;
const [result, errors] = item.call(null, current);
return [result, [...currentErrors, ...errors]];
},
[merged, []] as TransformResult
);
return output;
}
/**
* @param string
*/
export function explodeFilesArg(string): string {
return string.split(",").map(item => item.trim());
}
/**
* @param value
* @returns {{globs: Array, objs: Array}}
*/
export function makeFilesArg(value) {
let globs = [];
let objs = [];
if (_.isString(value)) {
globs = globs.concat(explodeFilesArg(value));
}
if (List.isList(value) && value.size) {
value.forEach(function(value) {
if (_.isString(value)) {
globs.push(value);
} else {
if (Map.isMap(value)) {
objs.push(value);
}
}
});
}
return {
globs: globs,
objs: objs
};
}
export function printErrors(errors: BsErrors) {
return errors
.map(error =>
[
`Error Type: ${error.type}`,
`Error Level: ${error.level}`,
error.errors.map(item =>
[
`Error Message: ${item.error.message}`,
item.meta ? item.meta().join("\n") : ""
]
.filter(Boolean)
.join("\n")
)
].join("\n")
)
.join("\n\n");
}
================================================
FILE: packages/browser-sync/lib/cli/command.init.js
================================================
"use strict";
var info = require("./cli-info");
/**
* $ browser-sync init
*
* This command will generate a configuration
* file in the current directory
*
* @param opts
*/
module.exports = function(opts) {
info.makeConfig(process.cwd(), opts.cb);
};
================================================
FILE: packages/browser-sync/lib/cli/command.recipe.js
================================================
"use strict";
var logger = require("../logger").logger;
var chalk = require("chalk");
/**
* $ browser-sync recipe
*
* This command will copy a recipe into either the current directory
* or one given with the --output flag
*
* @param opts
* @returns {Function}
*/
module.exports = function(opts) {
var path = require("path");
var fs = require("fs-extra");
var input = opts.cli.input.slice(1);
var resolved = require.resolve("bs-recipes");
var dir = path.dirname(resolved);
var logRecipes = function() {
var dirs = fs.readdirSync(path.join(dir, "recipes"));
logger.info(
"Install one of the following with %s\n",
chalk.cyan('browser-sync recipe ')
);
dirs.forEach(function(name) {
console.log(" " + name);
});
};
if (!input.length) {
logger.info("No recipe name provided!");
logRecipes();
return opts.cb();
}
if (opts.cli.input[1] === "ls") {
logRecipes();
return opts.cb();
}
input = input[0];
var flags = opts.cli.flags;
var output = flags.output
? path.resolve(flags.output)
: path.join(process.cwd(), input);
var targetDir = path.join(dir, "recipes", input);
if (fs.existsSync(output)) {
return opts.cb(
new Error("Target folder exists remove it first and then try again")
);
}
if (fs.existsSync(targetDir)) {
fs.copy(targetDir, output, function(err) {
if (err) {
opts.cb(err);
} else {
logger.info("Recipe copied into %s", chalk.cyan(output));
logger.info(
"Next, inside that folder, run %s",
chalk.cyan("npm i && npm start")
);
opts.cb(null);
}
});
} else {
logger.info(
"Recipe %s not found. The following are available though",
chalk.cyan(input)
);
logRecipes();
opts.cb();
}
};
================================================
FILE: packages/browser-sync/lib/cli/command.reload.js
================================================
"use strict";
/**
* $ browser-sync reload
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
module.exports = function(opts) {
var flags = opts.cli.flags;
if (!flags.url) {
flags.url = "http://localhost:" + (flags.port || 3000);
}
var proto = require("../http-protocol");
var scheme = flags.url.match(/^https/) ? "https" : "http";
var args = { method: "reload" };
if (flags.files) {
args.args = flags.files;
}
var url = proto.getUrl(args, flags.url);
if (scheme === "https") {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
require(scheme)
.get(url, function(res) {
res.on("data", function() {
if (res.statusCode === 200) {
opts.cb(null, res);
}
});
})
.on("error", function(err) {
if (err.code === "ECONNREFUSED") {
err.message = "Browsersync not running at " + flags.url;
}
return opts.cb(err);
});
};
================================================
FILE: packages/browser-sync/lib/cli/command.start.ts
================================================
import * as path from "path";
import { existsSync } from "fs";
import { fromJS } from "immutable";
import * as utils from "../utils";
import { explodeFilesArg } from "./cli-options";
const _ = require("../lodash.custom");
/**
* $ browser-sync start
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
export default function(opts) {
const flags = preprocessFlags(opts.cli.flags);
const cwd = flags.cwd || process.cwd();
const maybepkg = path.resolve(cwd, "package.json");
let input = flags;
if (flags.config) {
const maybeconf = path.resolve(cwd, flags.config);
if (existsSync(maybeconf)) {
const conf = require(maybeconf);
input = _.merge({}, conf, flags);
} else {
utils.fail(
true,
new Error(`Configuration file '${flags.config}' not found`),
opts.cb
);
}
} else {
if (existsSync(maybepkg)) {
const pkg = require(maybepkg);
if (pkg["browser-sync"]) {
console.log("> Configuration obtained from package.json");
input = _.merge({}, pkg["browser-sync"], flags);
}
}
}
return require("../")
.create("cli")
.init(input, opts.cb);
}
/**
* @param flags
* @returns {*}
*/
function preprocessFlags(flags) {
return [
stripUndefined,
legacyFilesArgs,
removeWatchBooleanWhenFalse
].reduce((flags, fn) => fn.call(null, flags), flags);
}
/**
* Incoming undefined values are problematic as
* they interfere with Immutable.Map.mergeDeep
* @param subject
* @returns {*}
*/
function stripUndefined(subject) {
return Object.keys(subject).reduce((acc, key) => {
const value = subject[key];
if (typeof value === "undefined") {
return acc;
}
acc[key] = value;
return acc;
}, {});
}
/**
* @param flags
* @returns {*}
*/
function legacyFilesArgs(flags) {
if (flags.files && flags.files.length) {
flags.files = flags.files.reduce(
(acc, item) => acc.concat(explodeFilesArg(item)),
[]
);
}
return flags;
}
/**
* `watch` is a CLI boolean so should be removed if false to
* allow config to set watch: true
* @param flags
* @returns {any}
*/
function removeWatchBooleanWhenFalse(flags) {
if (flags.watch === false) {
return fromJS(flags)
.delete("watch")
.toJS();
}
return flags;
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/addCwdToWatchOptions.ts
================================================
import { BsTempOptions, TransformResult } from "../cli-options";
export function addCwdToWatchOptions(incoming: BsTempOptions): TransformResult {
const output = incoming.updateIn(["watchOptions", "cwd"], watchCwd => {
return watchCwd || incoming.get("cwd");
});
return [output, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/addDefaultIgnorePatterns.ts
================================================
import { List } from "immutable";
import { BsTempOptions, TransformResult } from "../cli-options";
const defaultIgnorePatterns = [
/node_modules/,
/bower_components/,
".sass-cache",
".vscode",
".git",
".idea"
];
export function addDefaultIgnorePatterns(
incoming: BsTempOptions
): TransformResult {
if (!incoming.get("watch")) {
return [incoming, []];
}
const output = incoming.update("watchOptions", watchOptions => {
const userIgnored = List([])
.concat(watchOptions.get("ignored"))
.filter(Boolean)
.toSet();
const merged = userIgnored.merge(defaultIgnorePatterns);
return watchOptions.merge({
ignored: merged.toList()
});
});
return [output, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/addToFilesOption.ts
================================================
import { List, Map } from "immutable";
import { BsTempOptions, TransformResult } from "../cli-options";
export function addToFilesOption(incoming: BsTempOptions): TransformResult {
if (!incoming.get("watch")) {
return [incoming, []];
}
let serverPaths = [];
const fromServeStatic = incoming.get("serveStatic", List([])).toArray();
const ssPaths = fromServeStatic.reduce((acc, ss) => {
if (typeof ss === "string") {
return acc.concat(ss);
}
if (ss.dir && typeof ss.dir === "string") {
return acc.concat(ss);
}
return acc;
}, []);
ssPaths.forEach(p => serverPaths.push(p));
const server = incoming.get("server");
if (server) {
if (server === true) {
serverPaths.push(".");
}
if (typeof server === "string") {
serverPaths.push(server);
}
if (List.isList(server) && server.every(x => typeof x === "string")) {
server.forEach(s => serverPaths.push(s));
}
if (Map.isMap(server)) {
const baseDirProp = server.get("baseDir");
const baseDirs = List([])
.concat(baseDirProp)
.filter(Boolean);
baseDirs.forEach(s => serverPaths.push(s));
}
}
const output = incoming.update("files", files => {
return List([])
.concat(files, serverPaths)
.filter(Boolean);
});
return [output, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/appendServerDirectoryOption.ts
================================================
import { BsTempOptions, TransformResult } from "../cli-options";
export function appendServerDirectoryOption(
incoming: BsTempOptions
): TransformResult {
if (!incoming.get("server")) return [incoming, []];
if (incoming.get("directory")) {
return [
incoming.setIn(["server", "directory"], incoming.has("directory")),
[]
];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/appendServerIndexOption.ts
================================================
import { BsTempOptions, TransformResult } from "../cli-options";
export function appendServerIndexOption(
incoming: BsTempOptions
): TransformResult {
if (!incoming.get("server")) return [incoming, []];
const value = incoming.get("index");
if (value) {
return [incoming.setIn(["server", "index"], value), []];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/copyCLIIgnoreToWatchOptions.ts
================================================
import { List } from "immutable";
import { BsTempOptions, TransformResult } from "../cli-options";
export function copyCLIIgnoreToWatchOptions(
incoming: BsTempOptions
): TransformResult {
if (!incoming.get("ignore")) {
return [incoming, []];
}
const output = incoming.updateIn(
["watchOptions", "ignored"],
List([]),
ignored => {
return List([]).concat(ignored, incoming.get("ignore"));
}
);
return [output, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleExtensionsOption.ts
================================================
import { List } from "immutable";
import {
BsTempOptions,
explodeFilesArg,
TransformResult
} from "../cli-options";
const _ = require("../../lodash.custom");
export function handleExtensionsOption(
incoming: BsTempOptions
): TransformResult {
const value = incoming.get("extensions");
if (_.isString(value)) {
const split = explodeFilesArg(value);
if (split.length) {
return [incoming.set("extensions", List(split)), []];
}
}
if (List.isList(value)) {
return [incoming.set("extensions", value), []];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleFilesOption.ts
================================================
import { fromJS } from "immutable";
import { BsTempOptions, makeFilesArg, TransformResult } from "../cli-options";
import { FilesNamespaces } from "../../types";
export function handleFilesOption(incoming: BsTempOptions): TransformResult {
const value = incoming.get("files");
const namespaces: FilesNamespaces = {
core: {
globs: [],
objs: []
}
};
const processed = makeFilesArg(value);
if (processed.globs.length) {
namespaces.core.globs = processed.globs;
}
if (processed.objs.length) {
namespaces.core.objs = processed.objs;
}
return [incoming.set("files", fromJS(namespaces)), []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleGhostModeOption.ts
================================================
import { fromJS } from "immutable";
import { BsTempOptions, TransformResult } from "../cli-options";
export function handleGhostModeOption(
incoming: BsTempOptions
): TransformResult {
const value = incoming.get("ghostMode");
var trueAll = {
clicks: true,
scroll: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
};
var falseAll = {
clicks: false,
scroll: false,
forms: {
submit: false,
inputs: false,
toggles: false
}
};
if (value === false || value === "false") {
return [incoming.set("ghostMode", fromJS(falseAll)), []];
}
if (value === true || value === "true") {
return [incoming.set("ghostMode", fromJS(trueAll)), []];
}
if (value.get("forms") === false) {
return [
incoming.set(
"ghostMode",
value.withMutations(function(map) {
map.set(
"forms",
fromJS({
submit: false,
inputs: false,
toggles: false
})
);
})
),
[]
];
}
if (value.get("forms") === true) {
return [
incoming.set(
"ghostMode",
value.withMutations(function(map) {
map.set(
"forms",
fromJS({
submit: true,
inputs: true,
toggles: true
})
);
})
),
[]
];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleHostOption.ts
================================================
import { BsTempOptions, TransformResult } from "../cli-options";
import { BsErrorLevels, BsErrorTypes } from "../../bin";
export function handleHostOption(incoming: BsTempOptions): TransformResult {
const host: string | null = incoming.get("host");
const listen: string | null = incoming.get("listen");
if (host && listen) {
if (host !== listen) {
return [
incoming,
[
{
errors: [
{
error: new Error(
"Cannot specify both `host` and `listen` options"
),
meta() {
return [
"",
"Tip: Use just the `listen` option *only* if you want to bind only to a particular host."
];
}
}
],
level: BsErrorLevels.Fatal,
type: BsErrorTypes.HostAndListenIncompatible
}
]
];
}
// whenever we have have both `host` + `listen` options,
// we remove the 'host' to prevent complication further down the line
return [incoming.delete("host"), []];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handlePortsOption.ts
================================================
import { Map } from "immutable";
import { PortsOption } from "../../types";
import { BsTempOptions, TransformResult } from "../cli-options";
export function handlePortsOption(incoming: BsTempOptions): TransformResult {
const value = incoming.get("ports");
if (!value) return [incoming, []];
const obj: PortsOption = { min: null, max: null };
if (typeof value === "string") {
if (~value.indexOf(",")) {
const segs = value.split(",");
obj.min = parseInt(segs[0], 10);
obj.max = parseInt(segs[1], 10);
} else {
obj.min = parseInt(value, 10);
obj.max = null;
}
} else {
obj.min = value.get("min");
obj.max = value.get("max") || null;
}
return [incoming.set("ports", Map(obj)), []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleProxyOption.ts
================================================
import * as url from "url";
import { Map } from "immutable";
import { BrowsersyncProxy } from "../../types";
import { BsTempOptions, TransformResult } from "../cli-options";
export function handleProxyOption(incoming: BsTempOptions): TransformResult {
let value = incoming.get("proxy");
let mw;
let target;
if (!value || value === true) {
return [incoming, []];
}
if (typeof value !== "string") {
target = value.get("target");
mw = value.get("middleware");
} else {
target = value;
value = Map({});
}
if (!target.match(/^(https?):\/\//)) {
target = "http://" + target;
}
const parsedUrl = url.parse(target);
if (!parsedUrl.port) {
parsedUrl.port = "80";
}
const out: BrowsersyncProxy = {
target: parsedUrl.protocol + "//" + parsedUrl.host,
url: Map(parsedUrl)
};
if (mw) {
out.middleware = mw;
}
const proxyOutput = value.mergeDeep(out);
return [incoming.set("proxy", proxyOutput), []];
}
================================================
FILE: packages/browser-sync/lib/cli/transforms/handleServerOption.ts
================================================
import { IServerOption } from "../../types";
import { fromJS, List, Map } from "immutable";
import { BsTempOptions, TransformResult } from "../cli-options";
export function handleServerOption(incoming: BsTempOptions): TransformResult {
const value = incoming.get("server");
if (value === false) {
return [incoming, []];
}
// server: true
if (value === true) {
const obj: IServerOption = {
baseDir: ["./"]
};
return [incoming.set("server", fromJS(obj)), []];
}
// server: "./app"
if (typeof value === "string") {
const obj: IServerOption = {
baseDir: [value]
};
return [incoming.set("server", fromJS(obj)), []];
}
if (List.isList(value)) {
const obj: IServerOption = {
baseDir: value
};
return [incoming.set("server", fromJS(obj)), []];
}
if (Map.isMap(value)) {
const dirs = List([])
.concat(value.get("baseDir", "./"))
.filter(Boolean);
const merged = value.merge({ baseDir: dirs });
return [incoming.set("server", merged), []];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/config.js
================================================
"use strict";
var path = require("path");
/**
* @type {{controlPanel: {jsFile: string, baseDir: *}, socketIoScript: string, configFile: string, client: {shims: string}}}
*/
module.exports = {
controlPanel: {
jsFile: "/js/app.js",
baseDir: path.join(__dirname, "control-panel")
},
templates: {
scriptTag: path.join(__dirname, "..", "templates/script-tags.html"),
scriptTagSimple: path.join(
__dirname,
"..",
"templates/script-tags-simple.html"
),
connector: path.join(__dirname, "..", "templates/connector.tmpl")
},
socketIoScript: "/public/socket.io.min.1.6.0.js",
configFile: "default-config.js",
userFile: "bs-config.js",
template: path.join(__dirname, "..", "templates/cli-template.js"),
httpProtocol: {
path: "/__browser_sync__"
},
client: {
shims: "/client/client-shims.js"
},
errors: {
"server+proxy":
"Invalid config. You cannot specify both server & proxy options.",
"proxy+https":
"Invalid config. You set https: true, but your proxy target doesn't reflect this."
}
};
================================================
FILE: packages/browser-sync/lib/connect-utils.js
================================================
"use strict";
var _ = require("./lodash.custom");
var fs = require("fs");
var config = require("./config");
function getPath(options, relative, port) {
if (options.get("mode") === "snippet") {
return options.get("scheme") + "://HOST:" + port + relative;
} else {
return "//HOST:" + port + relative;
}
}
var connectUtils = {
/**
* Allow users to disable the Browsersync snippet
* @param {Immutable.Map} options
* @returns {Boolean}
*/
enabled: function(options) {
const userValue = options.get("snippet");
if (typeof userValue === "boolean") {
return userValue
}
return true
},
/**
* @param {Immutable.Map} options
* @returns {String}
*/
scriptTags: function(options) {
var scriptPath = this.clientScript(options);
var async = options.getIn(["snippetOptions", "async"]);
var scriptDomain = options.getIn(["script", "domain"]);
/**
* Generate the [src] attribute based on user options
*/
var scriptSrc = (function() {
if (options.get("localOnly")) {
return [
options.get("scheme"),
"://localhost:",
options.get("port"),
scriptPath
].join("");
}
/**
* First, was "scriptPath" set? if so the user wanted full control over the
* script tag output
*
*/
if (_.isFunction(options.get("scriptPath"))) {
return options
.get("scriptPath")
.apply(null, getScriptArgs(options, scriptPath));
}
/**
* Next, if "script.domain" was given, allow that + the path to the JS file
* eg:
* script.domain=localhost:3000
* -> localhost:3000/browser-sync/browser-sync-client.js
*/
if (scriptDomain) {
if (_.isFunction(scriptDomain)) {
return scriptDomain.call(null, options) + scriptPath;
}
if (scriptDomain.match(/\{port\}/)) {
return (
scriptDomain.replace("{port}", options.get("port")) +
scriptPath
);
}
return scriptDomain + scriptPath;
}
/**
* Now if server or proxy, use dynamic script
* eg:
* browser-sync start --server
* ->
* "HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
*/
if (options.get("server") || options.get("proxy")) {
return scriptPath;
}
/**
* Final use case is snippet mode
* -> "http://HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
* -> "//HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)"
*/
return getPath(options, scriptPath, options.get("port"));
})();
/**
* Decide which template shall be used to generate the script tags
*/
var template = (function() {
if (scriptDomain || options.get("localOnly")) {
return config.templates.scriptTagSimple;
}
return config.templates.scriptTag;
})();
/**
* Finally read the template file from disk and replace
* the dynamic values.
*/
return fs
.readFileSync(template, "utf8")
.replace("%script%", scriptSrc)
.replace("%async%", async ? "async" : "");
},
/**
* @param {Map} options
* @returns {String}
*/
socketConnector: function(options) {
var socket = options.get("socket");
var template = fs.readFileSync(config.templates.connector, "utf-8");
var url = connectUtils.getConnectionUrl(options);
/**
* ***Backwards compatibility***. While `socket.path` is technically a
* socketIoClientConfig property, it's been documented previously
* as a top-level option, so must stay.
*/
var clientConfig = socket.get("socketIoClientConfig").merge({
path: socket.get("path")
});
template = template
.replace("%config%", JSON.stringify(clientConfig.toJS()))
.replace("%options%", JSON.stringify(options))
.replace("%url%", url);
return template;
},
/**
* @param {Object} socketOpts
* @param {Map} options
* @returns {String|Function}
*/
getNamespace: function(socketOpts, options) {
var namespace = socketOpts.namespace;
if (typeof namespace === "function") {
return namespace(options);
}
if (!namespace.match(/^\//)) {
namespace = "/" + namespace;
}
return namespace;
},
/**
* @param {Map} options
* @returns {string}
*/
getConnectionUrl: function(options) {
var socketOpts = options.get("socket").toJS();
var namespace = connectUtils.getNamespace(socketOpts, options);
var protocol = "";
var withHostnamePort =
"'{protocol}' + location.hostname + ':{port}{ns}'";
var withHost = "'{protocol}' + location.host + '{ns}'";
var withDomain = "'{domain}{ns}'";
var port = options.get("port");
// default use-case is server/proxy
var string = withHost;
if (options.get("mode") !== "server") {
protocol = options.get("scheme") + "://";
string = withHostnamePort;
}
if (options.get("mode") === "proxy" && options.getIn(["proxy", "ws"])) {
port = options.getIn(["socket", "port"]);
}
/**
* Ensure socket.domain is always a string (for noop replacements later)
*/
socketOpts.domain = (function() {
if (options.get("localOnly")) {
string = withDomain;
return [
options.get("scheme"),
"://localhost:",
options.get("port")
].join("");
}
if (socketOpts.domain) {
string = withDomain;
/**
* User provided a function
*/
if (_.isFunction(socketOpts.domain)) {
return socketOpts.domain.call(null, options);
}
/**
* User provided a string
*/
if (_.isString(socketOpts.domain)) {
return socketOpts.domain;
}
}
return "";
})();
return string
.replace("{protocol}", protocol)
.replace("{port}", port)
.replace("{domain}", socketOpts.domain.replace("{port}", port))
.replace("{ns}", namespace);
},
/**
* @param {Object} [options]
* @param {Boolean} [both]
*/
clientScript: function(options, both) {
var prefix = options.getIn(["socket", "clientPath"]);
var script = prefix + "/browser-sync-client.js";
var versioned =
prefix + "/browser-sync-client.js?v=" + options.get("version");
if (both) {
return {
path: script,
versioned: versioned
};
}
return versioned;
}
};
/**
* @param options
* @returns {*[]}
*/
function getScriptArgs(options, scriptPath) {
var abspath =
options.get("scheme") + "://HOST:" + options.get("port") + scriptPath;
return [scriptPath, options.get("port"), options.set("absolute", abspath)];
}
module.exports = connectUtils;
================================================
FILE: packages/browser-sync/lib/default-config.js
================================================
/**
* @module BrowserSync.options
*/
module.exports = {
/**
* Browsersync includes a user-interface that is accessed via a separate port.
* The UI allows to controls all devices, push sync updates and much more.
* @property ui
* @type Object
* @param {Number} [port=3001]
* @since 2.0.0
* @default false
*/
ui: {
port: 3001
},
/**
* Browsersync can watch your files as you work. Changes you make will either
* be injected into the page (CSS & images) or will cause all browsers to do
* a full-page refresh.
* @property files
* @type Array|String
* @default false
*/
files: false,
/**
* Specify which file events to respond to.
* Available events: `add`, `change`, `unlink`, `addDir`, `unlinkDir`
* @property watchEvents
* @type Array
* @default ["change"]
* @since 2.18.8
*/
watchEvents: ["change"],
/**
* Watch files automatically - this should be used as an
* alternative to the `files` option. When this option is used, some directories
* will be ignored automatically such as `node_modules` `bower_components` `.sass-cache`
* `.vscode` `.git` `.idea`
*
* @property watch
* @type Boolean
* @default false
* @since 2.23.0
*/
watch: false,
/**
* Patterns for any watchers to ignore. Anything provided here
* will end up inside `watchOptions.ignored`
* @property ignore
* @type Array
* @default []
* @since 2.23.0
*/
ignore: [],
/**
* Serve an index.html file for all non-asset routes. Useful
* when using client-routers
* @property single
* @type Boolean
* @default false
* @since 2.23.0
*/
single: false,
/**
* File watching options that get passed along to [Chokidar](https://github.com/paulmillr/chokidar).
* Check their docs for available options
* @property watchOptions
* @type Object
* @default undefined
* @since 2.6.0
*/
watchOptions: {
ignoreInitial: true
/*
persistent: true,
ignored: '*.txt',
followSymlinks: true,
cwd: '.',
usePolling: true,
alwaysStat: false,
depth: undefined,
interval: 100,
ignorePermissionErrors: false,
atomic: true
*/
},
/**
* Use the built-in static server for basic HTML/JS/CSS websites.
* @property server
* @type Object|Boolean
* @default false
*/
server: false,
/**
* Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
* @property proxy
* @type String|Object|Boolean
* @param {String} [target]
* @param {Boolean} [ws] - Enable websocket proxying
* @param {Function|Array} [middleware]
* @param {Function} [reqHeaders]
* @param {Array} [proxyReq]
* @param {Array} [proxyRes]
* @default false
*/
proxy: false,
/**
* @property port
* @type Number
* @default 3000
*/
port: 3000,
/**
* @property middleware
* @type Function|Array
* @default false
*/
middleware: false,
/**
* Add additional directories from which static
* files should be served. Should only be used in `proxy` or `snippet`
* mode.
* @property serveStatic
* @type Array
* @default []
* @since 2.8.0
*/
serveStatic: [],
/**
* Options that are passed to the serve-static middleware
* when you use the string[] syntax: eg: `serveStatic: ['./app']`. Please see
* [serve-static](https://github.com/expressjs/serve-static) for details
*
* @property serveStaticOptions
* @type Object
* @since 2.17.0
*/
/**
* Enable https for localhost development. **Note** - this is not needed for proxy
* option as it will be inferred from your target url.
* @property https
* @type Boolean
* @default undefined
* @since 1.3.0
*/
/**
* Override http module to allow using 3rd party server modules (such as http2)
* *Note*: these modules are not included in the Browsersync package - you need
* to 'npm install' any that you'd like to use.
* @property httpModule
* @type string
* @default undefined
* @since 2.18.0
*/
/**
* Current working directory
* @property cwd
* @type String
* @since 2.23.0
*/
/**
* Register callbacks via a regular option - this can be used
* to get access the Browsersync instance in situations where you
* cannot provide a callback via the normal API (for example, in a Gruntfile)
*
* **Note**: Only the `ready` callback is currently supported here.
*
* @property callbacks
* @type Object
* @param {Function} ready
*/
/**
* Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
* @property ghostMode
* @param {Boolean} [clicks=true]
* @param {Boolean} [scroll=true]
* @param {Boolean} [location=true]
* @param {Boolean} [forms=true]
* @param {Boolean} [forms.submit=true]
* @param {Boolean} [forms.inputs=true]
* @param {Boolean} [forms.toggles=true]
* @type Object
*/
ghostMode: {
clicks: true,
scroll: true,
location: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
},
/**
* Can be either "info", "debug", "warn", or "silent"
* @property logLevel
* @type String
* @default info
*/
logLevel: "info",
/**
* Change the console logging prefix. Useful if you're creating your
* own project based on Browsersync
* @property logPrefix
* @type String
* @default Browsersync
* @since 1.5.1
*/
logPrefix: "Browsersync",
/**
* @property logConnections
* @type Boolean
* @default false
*/
logConnections: false,
/**
* @property logFileChanges
* @type Boolean
* @default true
*/
logFileChanges: true,
/**
* Log the snippet to the console when you're in snippet mode (no proxy/server)
* @property logSnippet
* @type: Boolean
* @default true
* @since 1.5.2
*/
logSnippet: true,
/**
* You can prevent Browsersync from injecting the connection snippet
* by passing `snippet: false`.
* @property snippet
* @type Boolean
* @default undefined
*/
/**
* You can control how the snippet is injected
* onto each page via a custom regex + function.
* You can also provide patterns for certain urls
* that should be ignored from the snippet injection.
* @property snippetOptions
* @since 2.0.0
* @param {Boolean} [async] - should the script tags have the async attribute?
* @param {Array} [blacklist]
* @param {Array} [whitelist]
* @param {RegExp} [rule.match=/$/]
* @param {Function} [rule.fn=Function]
* @type Object
*/
snippetOptions: {
async: true,
whitelist: [],
blacklist: [],
rule: {
match: /]*>/i,
fn: function(snippet, match) {
return match + snippet;
}
}
},
/**
* Add additional HTML rewriting rules.
* @property rewriteRules
* @since 2.4.0
* @type Array
* @default false
*/
rewriteRules: [],
/**
* @property tunnel
* @type String|Boolean
* @default null
*/
/**
* Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
* Can be `true`, `local`, `external`, `ui`, `ui-external`, `tunnel` or `false`
* @property open
* @type Boolean|String
* @default true
*/
open: "local",
/**
* @property browser
* @type String|Array
* @default default
*/
browser: "default",
/**
* Add HTTP access control (CORS) headers to assets served by Browsersync.
* @property cors
* @type boolean
* @default false
* @since 2.16.0
*/
cors: false,
hostnameSuffix: false,
/**
* Reload each browser when Browsersync is restarted.
* @property reloadOnRestart
* @type Boolean
* @default false
*/
reloadOnRestart: false,
/**
* The small pop-over notifications in the browser are not always needed/wanted.
* @property notify
* @type Boolean
* @default true
*/
notify: true,
/**
* @property scrollProportionally
* @type Boolean
* @default true
*/
scrollProportionally: true,
/**
* @property scrollThrottle
* @type Number
* @default 0
*/
scrollThrottle: 0,
/**
* Decide which technique should be used to restore
* scroll position following a reload.
* Can be `window.name` or `cookie`
* @property scrollRestoreTechnique
* @type String
* @default 'window.name'
*/
scrollRestoreTechnique: "window.name",
/**
* Sync the scroll position of any element
* on the page. Add any amount of CSS selectors
* @property scrollElements
* @type Array
* @default []
* @since 2.9.0
*/
scrollElements: [],
/**
* Sync the scroll position of any element
* on the page - where any scrolled element
* will cause all others to match scroll position.
* This is helpful when a breakpoint alters which element
* is actually scrolling
* @property scrollElementMapping
* @type Array
* @default []
* @since 2.9.0
*/
scrollElementMapping: [],
/**
* Time, in milliseconds, to wait before
* instructing the browser to reload/inject following a
* file change event
* @property reloadDelay
* @type Number
* @default 0
*/
reloadDelay: 0,
/**
* Wait for a specified window of event-silence before
* sending any reload events.
* @property reloadDebounce
* @type Number
* @default 0
* @since 2.6.0
*/
reloadDebounce: 500,
/**
* Emit only the first event during sequential time windows
* of a specified duration.
* @property reloadThrottle
* @type Number
* @default 0
* @since 2.13.0
*/
reloadThrottle: 0,
/**
* User provided plugins
* @property plugins
* @type Array
* @default []
* @since 2.6.0
*/
plugins: [],
/**
* @property injectChanges
* @type Boolean
* @default true
*/
injectChanges: true,
/**
* @property startPath
* @type String|Null
* @default null
*/
startPath: null,
/**
* Whether to minify client script, or not.
* @property minify
* @type Boolean
* @default true
*/
minify: true,
/**
* @property host
* @type String
* @default null
*/
host: null,
/**
* Specify a host to listen on. Use this if you want to
* prevent binding to all interfaces.
*
* Note: When you specify this option, it overrides the 'host' option
* @property listen
* @type String
* @default undefined
*/
/**
* Support environments where dynamic hostnames are not required
* (ie: electron)
* @property localOnly
* @type Boolean
* @default false
* @since 2.14.0
*/
localOnly: false,
/**
* @property codeSync
* @type Boolean
* @default true
*/
codeSync: true,
/**
* @property timestamps
* @type Boolean
* @default true
*/
timestamps: true,
clientEvents: [
"scroll",
"scroll:element",
"input:text",
"input:toggles",
"form:submit",
"form:reset",
"click"
],
/**
* Alter the script path for complete control over where the Browsersync
* Javascript is served from. Whatever you return from this function
* will be used as the script path.
* @property scriptPath
* @default undefined
* @since 1.5.0
* @type Function
*/
/**
* Configure the Socket.IO path and namespace & domain to avoid collisions.
* @property socket
* @param {String} [path="/browser-sync/socket.io"]
* @param {String} [clientPath="/browser-sync"]
* @param {String|Function} [namespace="/browser-sync"]
* @param {String|Function} [domain=undefined]
* @param {String|Function} [port=undefined]
* @param {Object} [clients.heartbeatTimeout=5000]
* @since 1.6.2
* @type Object
*/
socket: {
socketIoOptions: {
log: false
},
socketIoClientConfig: {
reconnectionAttempts: 50
},
path: "/browser-sync/socket.io",
clientPath: "/browser-sync",
namespace: "/browser-sync",
clients: {
heartbeatTimeout: 5000
}
},
/**
* Configure the script domain
* @property script
* @param {String|Function} [domain=undefined]
* @since 2.14.0
* @type Object
*/
tagNames: {
less: "link",
scss: "link",
css: "link",
jpg: "img",
jpeg: "img",
png: "img",
svg: "img",
gif: "img",
js: "script"
},
injectFileTypes: ["css", "png", "jpg", "jpeg", "svg", "gif", "webp", "map"],
injectNotification: false, // console | overlay
excludedFileTypes: [
"js",
"css",
"pdf",
"map",
"svg",
"ico",
"woff",
"json",
"eot",
"ttf",
"png",
"jpg",
"jpeg",
"webp",
"gif",
"mp4",
"mp3",
"3gp",
"ogg",
"ogv",
"webm",
"m4a",
"flv",
"wmv",
"avi",
"swf",
"scss"
]
};
================================================
FILE: packages/browser-sync/lib/file-event-handler.js
================================================
var utils = require("./utils");
/**
* Apply the operators that apply to the 'file:changed' event
* @param {Rx.Observable} subject
* @param options
* @return {Rx.Observable<{type: string, files: Array}>}
*/
function fileChanges(subject, options) {
const operators = [
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
const scheduler = options.getIn(["debug", "scheduler"]);
/**
* Create a stream buffered/debounced stream of events
*/
const initial = getAggregatedDebouncedStream(subject, options, scheduler);
return applyOperators(operators, initial, options, scheduler).map(function(
items
) {
const paths = items.map(x => x.path);
if (
utils.willCauseReload(paths, options.get("injectFileTypes").toJS())
) {
return {
type: "reload",
files: items
};
}
return {
type: "inject",
files: items
};
});
}
module.exports.fileChanges = fileChanges;
/**
* Apply the operators that apply to the 'browser:reload' event
* @param {Rx.Observable} subject
* @param options
* @returns {Rx.Observable}
*/
function applyReloadOperators(subject, options) {
var operators = [
{
option: "reloadDebounce",
fnName: "debounce"
},
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
return applyOperators(
operators,
subject,
options,
options.getIn(["debug", "scheduler"])
);
}
module.exports.applyReloadOperators = applyReloadOperators;
/**
* @param items
* @param subject
* @param options
* @param scheduler
*/
function applyOperators(items, subject, options, scheduler) {
return items.reduce(function(subject, item) {
var value = options.get(item.option);
if (value > 0) {
return subject[item.fnName].call(subject, value, scheduler);
}
return subject;
}, subject);
}
/**
* @param subject
* @param options
* @param scheduler
*/
function getAggregatedDebouncedStream(subject, options, scheduler) {
return subject
.filter(function(x) {
return options.get("watchEvents").indexOf(x.event) > -1;
})
.buffer(subject.debounce(options.get("reloadDebounce"), scheduler));
}
================================================
FILE: packages/browser-sync/lib/file-utils.js
================================================
"use strict";
var _ = require("./lodash.custom");
var fileUtils = {
/**
* React to file-change events that occur on "core" namespace only
* @param bs
* @param data
*/
changedFile: function(bs, data) {
/**
* If the event property is undefined, infer that it's a 'change'
* event due the fact this handler is for emitter.emit("file:changed")
*/
if (_.isUndefined(data.event)) {
data.event = "change";
}
/**
* Chokidar always sends an 'event' property - which could be
* `add` `unlink` etc etc so we need to check for that and only
* respond to 'change', for now.
*/
if (bs.options.get("watchEvents").indexOf(data.event) > -1) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit(
"file:reload",
fileUtils.getFileInfo(data, bs.options)
);
}
}
},
/**
* @param data
* @param options
* @returns {{assetFileName: *, fileExtension: String}}
*/
getFileInfo: function(data, options) {
data.ext = require("path")
.extname(data.path)
.slice(1);
data.basename = require("path").basename(data.path);
var obj = {
ext: data.ext,
path: data.path,
basename: data.basename,
event: data.event,
type: "inject"
};
// RELOAD page
if (!_.includes(options.get("injectFileTypes").toJS(), obj.ext)) {
obj.url = obj.path;
obj.type = "reload";
}
obj.path = data.path;
obj.log = data.log;
return obj;
}
};
module.exports = fileUtils;
================================================
FILE: packages/browser-sync/lib/file-watcher.js
================================================
"use strict";
var _ = require("./lodash.custom");
var utils = require("./utils");
var Rx = require("rx");
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
module.exports.plugin = function(bs) {
var options = bs.options;
var emitter = bs.emitter;
var defaultWatchOptions = options.get("watchOptions").toJS();
return options.get("files").reduce(function(map, glob, namespace) {
/**
* Default CB when not given
* @param event
* @param path
*/
var fn = function(event, path) {
emitter.emit("file:changed", {
event: event,
path: path,
namespace: namespace
});
};
var jsItem = glob.toJS();
if (jsItem.globs.length) {
var watcher = watch(jsItem.globs, defaultWatchOptions, fn);
map[namespace] = {
watchers: [watcher]
};
}
if (jsItem.objs.length) {
jsItem.objs.forEach(function(item) {
if (!_.isFunction(item.fn)) {
item.fn = fn;
}
var watcher = watch(
item.match,
item.options || defaultWatchOptions,
item.fn.bind(bs.publicInstance)
);
if (!map[namespace]) {
map[namespace] = {
watchers: [watcher]
};
} else {
map[namespace].watchers.push(watcher);
}
});
}
return map;
}, {});
};
/**
* @param patterns
* @param opts
* @param cb
* @returns {*}
*/
function watch(patterns, opts, cb) {
if (typeof opts === "function") {
cb = opts;
opts = {};
}
var watcher = require("chokidar").watch(patterns, opts);
if (_.isFunction(cb)) {
watcher.on("all", cb);
}
// watcher.on('ready', () => {
// console.log(watcher.getWatched());
// });
return watcher;
}
module.exports.watch = watch;
================================================
FILE: packages/browser-sync/lib/hooks.js
================================================
"use strict";
var _ = require("./lodash.custom");
var Immutable = require("immutable");
var snippetUtils = require("./snippet").utils;
module.exports = {
/**
*
* @this {BrowserSync}
* @returns {String}
*/
"client:js": function(hooks, data) {
var js = snippetUtils.getClientJs(data.port, data.options);
return hooks.reduce(
function(acc, hook) {
if (typeof hook === "function") {
return acc.concat(hook);
}
return acc.concat(String(hook));
},
[js]
);
},
/**
* @this {BrowserSync}
* @returns {Array}
*/
"client:events": function(hooks, clientEvents) {
hooks.forEach(function(hook) {
var result = hook(this);
if (Array.isArray(result)) {
clientEvents = _.union(clientEvents, result);
} else {
clientEvents.push(result);
}
}, this);
return clientEvents;
},
/**
* @returns {Array}
*/
"server:middleware": function(hooks, initial) {
initial = initial || [];
_.each(
hooks,
function(hook) {
var result = hook(this);
if (Array.isArray(result)) {
result.forEach(function(res) {
if (_.isFunction(res)) {
initial = initial.push(res);
}
});
} else {
if (_.isFunction(result)) {
initial = initial.push(result);
}
}
},
this
);
return initial;
},
/**
* @param {Array} hooks
* @param {Map|List} initial
* @param pluginOptions
* @returns {any}
*/
"files:watch": function(hooks, initial, pluginOptions) {
var opts;
if (pluginOptions) {
opts = Immutable.fromJS(pluginOptions);
opts.forEach(function(value, key) {
if (!value) {
return;
}
var files = value.get("files");
if (files) {
var fileArg = require("./cli/cli-options").makeFilesArg(
files
);
if (fileArg) {
initial = initial.set(key, Immutable.fromJS(fileArg));
}
}
});
}
return initial;
}
};
================================================
FILE: packages/browser-sync/lib/http-protocol.js
================================================
"use strict";
var proto = exports;
var instanceMethods = ["exit", "notify", "pause", "resume"];
var getBody = require("raw-body");
const { parseParams, serializeParams } = require("./utils");
const permittedSocketEvents = [
"file:reload",
"browser:reload",
"browser:notify",
"browser:location",
"options:set"
];
/**
* Does the requested method expect an instance of BrowserSync
* or raw access to the emitter?
* @param method
* @returns {boolean}
*/
function methodRequiresInstance(method) {
return instanceMethods.indexOf(method) > -1;
}
/**
* Use BrowserSync options + querystring to create a
* full HTTP/HTTTPS url.
*
* Eg. http://localhost:3000/__browser_sync__?method=reload
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css&args=core.min
*
* @param args
* @param url
* @returns {string}
*/
proto.getUrl = function(args, url) {
return [
url,
require("./config").httpProtocol.path,
"?",
serializeParams(args).toString()
].join("");
};
/**
* Return a middleware for handling the requests
* @param {BrowserSync} bs
* @returns {Function}
*/
proto.middleware = function(bs) {
return function(req, res) {
if (req.method === "POST") {
return getBody(req, function(err, body) {
if (err) {
const output = ["Error: could not parse JSON."];
res.writeHead(500, { "Content-Type": "text/plain" });
return res.end(output.join("\n"));
}
try {
const [name, payload] = JSON.parse(body.toString());
bs.io.sockets.emit(name, payload);
return res.end(
`Browsersync HTTP Protocol received: ${name} ${JSON.stringify(
payload
)}`
);
} catch (e) {
const output = [`Error: ${e.message}`];
res.writeHead(500, { "Content-Type": "text/plain" });
return res.end(output.join("\n"));
}
});
}
var split = req.url.split("?");
var params = parseParams(split[1]);
var output;
if (!Object.keys(params).length) {
output = [
"Error: No Parameters were provided.",
"Example: http://localhost:3000/__browser_sync__?method=reload&args=core.css"
];
res.writeHead(500, { "Content-Type": "text/plain" });
res.end(output.join("\n"));
return;
}
try {
var bsOrEmitter = methodRequiresInstance(params.method)
? bs
: bs.events;
require("./public/" + params.method)(bsOrEmitter).apply(null, [
params.args
]);
output = [
"Called public API method `.%s()`".replace("%s", params.method),
"With args: " + JSON.stringify(params.args)
];
res.end(output.join("\n"));
} catch (e) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("Public API method `" + params.method + "` not found.");
res.end();
return;
}
};
};
================================================
FILE: packages/browser-sync/lib/index.js
================================================
#! /usr/bin/env node
"use strict";
/**
* @module BrowserSync
*/
var pjson = require("../package.json");
var BrowserSync = require("./browser-sync");
var publicUtils = require("./public/public-utils");
var events = require("events");
var chalk = require("chalk");
var PassThrough = require("stream").PassThrough;
var logger = require("eazy-logger").Logger({
useLevelPrefixes: true
});
var singleton = false;
var singletonPlugins = [];
var instances = [];
/**
* @type {boolean|EventEmitter}
*/
var singletonEmitter = false;
module.exports = initSingleton;
/**
* Create a Browsersync instance
* @method create
* @param {String} name an identifier that can used for retrieval later
*/
/**
* Get a single instance by name. This is useful if you have your
* build scripts in separate files
* @method get
* @param {String} name
* @returns {Object|Boolean}
*/
module.exports.get = function(name) {
var instance = getSingle(name);
if (instance) {
return instance;
}
throw new Error(
"An instance with the name `%s` was not found.".replace("%s", name)
);
};
/**
* Check if an instance has been created.
* @method has
* @param {String} name
* @returns {Boolean}
*/
module.exports.has = function(name) {
var instance = getSingle(name);
if (instance) {
return true;
}
return false;
};
/**
* Start the Browsersync service. This will launch a server, proxy or start the snippet
* mode depending on your use-case.
* @method init
* @param {Object} [config] This is the main configuration for your Browsersync instance and can contain any of the [available options]({{site.links.options}})
* If you do not pass a config an argument for configuration, Browsersync will still run; but it will be in the `snippet` mode
* @param {Function} [cb] If you pass a callback function, it will be called when Browsersync has completed all setup tasks and is ready to use. This
* is useful when you need to wait for information (for example: urls, port etc) or perform other tasks synchronously.
* @returns {BrowserSync}
*/
module.exports.init = initSingleton;
/**
* Register a plugin. Must implement at least a 'plugin' method that returns a
* callable function.
*
* @method use
* @param {String} name The name of the plugin
* @param {Object} module The object to be `required`.
* @param {Function} [cb] A callback function that will return any errors.
*/
module.exports.use = function() {
var args = Array.prototype.slice.call(arguments);
singletonPlugins.push({
args: args
});
};
/**
* The `reload` method will inform all browsers about changed files and will either cause the browser to refresh, or inject the files where possible.
*
* @method reload
* @param {String|Array|Object} [arg] The file or files to be reloaded.
* @returns {*}
*/
module.exports.reload = noop("reload");
/**
* The `stream` method returns a transform stream and can act once or on many files.
*
* @method stream
* @param {Object} [opts] Configuration for the stream method
* @param {Object} [opts.match] Resulting files to reload. The path is from the
* root of the site (not the root of your project). You can use '**' to recurse
* directories.
* @param {Object} [opts.once] Only reload on the first changed file in the stream.
* @since 2.6.0
* @returns {*}
*/
module.exports.stream = noop("stream");
/**
* Helper method for browser notifications
*
* @method notify
* @param {String|HTML} msg Can be a simple message such as 'Connected' or HTML
* @param {Number} [timeout] How long the message will remain in the browser. @since 1.3.0
*/
module.exports.notify = noop("notify");
/**
* This method will close any running server, stop file watching & exit the current process.
*
* @method exit
*/
module.exports.exit = noop("exit");
/**
* Stand alone file-watcher. Use this along with Browsersync to create your own, minimal build system
* @method watch
* @param {string} patterns Glob patterns for files to watch
* @param {object} [opts] Options to be passed to Chokidar - check what's available in [their docs](https://github.com/paulmillr/chokidar#getting-started)
* @param {function} [fn] Callback function for each event.
* @since 2.6.0
*/
module.exports.watch = noop("watch");
/**
* Method to pause file change events
*
* @method pause
*/
module.exports.pause = noop("pause");
/**
* Method to resume paused watchers
*
* @method resume
*/
module.exports.resume = noop("resume");
/**
* Add properties fo
*/
Object.defineProperties(module.exports, {
/**
* The internal Event Emitter used by the running Browsersync instance (if there is one).
* You can use this to emit your own events, such as changed files, logging etc.
*
* @property emitter
*/
emitter: {
get: function() {
if (!singletonEmitter) {
singletonEmitter = newEmitter();
return singletonEmitter;
}
return singletonEmitter;
}
},
/**
* A simple true/false flag that you can use to determine if there's a currently-running Browsersync instance.
*
* @property active
*/
active: {
get: getSingletonValue.bind(null, "active")
},
/**
* A simple true/false flag to determine if the current instance is paused
*
* @property paused
*/
paused: {
get: getSingletonValue.bind(null, "paused")
}
});
/**
* Event emitter factory
* @returns {EventEmitter}
*/
function newEmitter() {
var emitter = new events.EventEmitter();
emitter.setMaxListeners(20);
return emitter;
}
/**
* Get the singleton's emitter, or a new one.
* @returns {EventEmitter}
*/
function getSingletonEmitter() {
if (singletonEmitter) {
return singletonEmitter;
}
singletonEmitter = newEmitter();
return singletonEmitter;
}
/**
* Helper to allow methods to be called on the module export
* before there's a running instance
* @param {String} name
* @returns {Function}
*/
function noop(name) {
return function() {
var args = Array.prototype.slice.call(arguments);
if (singleton) {
return singleton[name].apply(singleton, args);
} else {
if (publicUtils.isStreamArg(name, args)) {
return new PassThrough({ objectMode: true });
}
}
};
}
/**
* Create a single instance when module export is used directly via browserSync({});
* This is mostly for back-compatibility, for also for the nicer api.
* This will never be removed to ensure we never break user-land, but
* we should discourage it's use.
* @returns {*}
*/
function initSingleton() {
var instance;
if (instances.length) {
instance = instances.filter(function(item) {
return item.name === "singleton";
});
if (instance.length) {
logger.error(
chalk.yellow("You tried to start Browsersync twice!"),
"To create multiple instances, use",
chalk.cyan("browserSync.create().init()")
);
return instance;
}
}
var args = Array.prototype.slice.call(arguments);
singleton = create("singleton", getSingletonEmitter());
if (singletonPlugins.length) {
singletonPlugins.forEach(function(obj) {
singleton.instance.registerPlugin.apply(
singleton.instance,
obj.args
);
});
}
singleton.init.apply(null, args);
return singleton;
}
/**
* @param {String} prop
* @returns {Object|Boolean}
*/
function getSingletonValue(prop) {
var single = getSingle("singleton");
if (single) {
return single[prop];
}
return false;
}
/**
* Get a single instance by name
* @param {String} name
* @returns {Object|Boolean}
*/
function getSingle(name) {
if (instances.length) {
var match = instances.filter(function(item) {
return item.name === name;
});
if (match.length) {
return match[0];
}
}
return false;
}
/**
* Create an instance of Browsersync
* @param {String} [name]
* @param {EventEmitter} [emitter]
* @returns {{init: *, exit: (exit|exports), notify: *, reload: *, cleanup: *, emitter: (Browsersync.events|*), use: *}}
*/
/**
* Reset the state of the module.
* (should only be needed for test environments)
*/
module.exports.reset = function() {
instances.forEach(function(item) {
item.cleanup();
});
instances = [];
singletonPlugins = [];
singletonEmitter = false;
singleton = false;
};
/**
* @type {Array}
*/
module.exports.instances = instances;
/**
* Create an instance of Browsersync
* @param {String} [name]
* @param {EventEmitter} [emitter]
* @returns {{init: *, exit: (exit|exports), notify: *, reload: *, cleanup: *, emitter: (Browsersync.events|*), use: *}}
*/
module.exports.create = create;
function create(name, emitter) {
name = name || new Date().getTime();
emitter = emitter || newEmitter();
var browserSync = new BrowserSync(emitter);
var instance = {
name: name,
instance: browserSync,
exit: require("./public/exit")(browserSync),
notify: require("./public/notify")(browserSync),
pause: require("./public/pause")(browserSync),
resume: require("./public/resume")(browserSync),
reload: require("./public/reload")(emitter),
stream: require("./public/stream")(emitter),
cleanup: browserSync.cleanup.bind(browserSync),
use: browserSync.registerPlugin.bind(browserSync),
getOption: browserSync.getOption.bind(browserSync),
emitter: browserSync.events,
watch: require("./file-watcher").watch
};
browserSync.publicInstance = instance;
instance.init = require("./public/init")(browserSync, name, pjson);
Object.defineProperty(instance, "active", {
get: function() {
return browserSync.active;
}
});
Object.defineProperty(instance, "paused", {
get: function() {
return browserSync.paused;
}
});
/**
* Access to client-side socket for emitting events
*
* @property sockets
*/
Object.defineProperty(instance, "sockets", {
get: function() {
if (!browserSync.active) {
return {
emit: function() {},
on: function() {}
};
} else {
return browserSync.io.sockets;
}
}
});
instances.push(instance);
return instance;
}
================================================
FILE: packages/browser-sync/lib/internal-events.js
================================================
"use strict";
var utils = require("./utils");
var fileUtils = require("./file-utils");
var Rx = require("rx");
var fromEvent = Rx.Observable.fromEvent;
var fileHandler = require("./file-event-handler");
module.exports = function(bs) {
var events = {
/**
* File reloads
* @param data
*/
"file:reload": function(data) {
bs.io.sockets.emit("file:reload", data);
},
/**
* Browser Reloads
*/
"browser:reload": function() {
bs.io.sockets.emit("browser:reload");
},
/**
* Browser Notify
* @param data
*/
"browser:notify": function(data) {
bs.io.sockets.emit("browser:notify", data);
},
/**
* Things that happened after the service is running
* @param data
*/
"service:running": function(data) {
var mode = bs.options.get("mode");
var open = bs.options.get("open");
if (
mode === "proxy" ||
mode === "server" ||
open === "ui" ||
open === "ui-external"
) {
utils.openBrowser(data.url, bs.options, bs);
}
// log about any file watching
if (bs.watchers) {
bs.events.emit("file:watching", bs.watchers);
}
},
/**
* Option setting
* @param data
*/
"options:set": function(data) {
if (bs.io) {
bs.io.sockets.emit("options:set", data);
}
},
/**
* Plugin configuration setting
* @param data
*/
"plugins:configure": function(data) {
if (data.active) {
bs.pluginManager.enablePlugin(data.name);
} else {
bs.pluginManager.disablePlugin(data.name);
}
bs.setOption("userPlugins", bs.getUserPlugins());
},
"plugins:opts": function(data) {
if (bs.pluginManager.pluginOptions[data.name]) {
bs.pluginManager.pluginOptions[data.name] = data.opts;
bs.setOption("userPlugins", bs.getUserPlugins());
}
}
};
Object.keys(events).forEach(function(event) {
bs.events.on(event, events[event]);
});
var reloader = fileHandler
.applyReloadOperators(
fromEvent(bs.events, "_browser:reload"),
bs.options
)
.subscribe(function() {
bs.events.emit("browser:reload");
});
var coreNamespacedWatchers = fromEvent(bs.events, "file:changed")
.filter(function() {
return bs.options.get("codeSync");
})
.filter(function(x) {
return x.namespace === "core";
});
var handler = fileHandler
.fileChanges(coreNamespacedWatchers, bs.options)
.subscribe(function(x) {
if (x.type === "reload") {
bs.events.emit("browser:reload", x);
}
if (x.type === "inject") {
x.files.forEach(function(data) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit(
"file:reload",
fileUtils.getFileInfo(data, bs.options)
);
}
});
}
});
bs.registerCleanupTask(function() {
handler.dispose();
reloader.dispose();
});
};
================================================
FILE: packages/browser-sync/lib/lodash.custom.js
================================================
/**
* @license
* Lodash (Custom Build)
* Build: `lodash include="isUndefined,isFunction,toArray,includes,union,each,isString,merge,isObject,set" exports="node"`
* Copyright JS Foundation and other contributors
* Released under MIT license
* Based on Underscore.js 1.8.3
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
(function() {
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
var undefined;
/** Used as the semantic version number. */
var VERSION = "4.17.4";
/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;
/** Error message constants. */
var FUNC_ERROR_TEXT = "Expected a function";
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = "__lodash_hash_undefined__";
/** Used as the maximum memoize cache size. */
var MAX_MEMOIZE_SIZE = 500;
/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
CLONE_FLAT_FLAG = 2,
CLONE_SYMBOLS_FLAG = 4;
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1,
COMPARE_UNORDERED_FLAG = 2;
/** Used to detect hot functions by number of calls within a span of milliseconds. */
var HOT_COUNT = 800,
HOT_SPAN = 16;
/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0,
MAX_SAFE_INTEGER = 9007199254740991,
MAX_INTEGER = 1.7976931348623157e308,
NAN = 0 / 0;
/** `Object#toString` result references. */
var argsTag = "[object Arguments]",
arrayTag = "[object Array]",
asyncTag = "[object AsyncFunction]",
boolTag = "[object Boolean]",
dateTag = "[object Date]",
errorTag = "[object Error]",
funcTag = "[object Function]",
genTag = "[object GeneratorFunction]",
mapTag = "[object Map]",
numberTag = "[object Number]",
nullTag = "[object Null]",
objectTag = "[object Object]",
promiseTag = "[object Promise]",
proxyTag = "[object Proxy]",
regexpTag = "[object RegExp]",
setTag = "[object Set]",
stringTag = "[object String]",
symbolTag = "[object Symbol]",
undefinedTag = "[object Undefined]",
weakMapTag = "[object WeakMap]";
var arrayBufferTag = "[object ArrayBuffer]",
dataViewTag = "[object DataView]",
float32Tag = "[object Float32Array]",
float64Tag = "[object Float64Array]",
int8Tag = "[object Int8Array]",
int16Tag = "[object Int16Array]",
int32Tag = "[object Int32Array]",
uint8Tag = "[object Uint8Array]",
uint8ClampedTag = "[object Uint8ClampedArray]",
uint16Tag = "[object Uint16Array]",
uint32Tag = "[object Uint32Array]";
/** Used to match property names within property paths. */
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
reIsPlainProp = /^\w*$/,
reLeadingDot = /^\./,
rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to match leading and trailing whitespace. */
var reTrim = /^\s+|\s+$/g;
/** Used to match backslashes in property paths. */
var reEscapeChar = /\\(\\)?/g;
/** Used to match `RegExp` flags from their coerced string values. */
var reFlags = /\w*$/;
/** Used to detect bad signed hexadecimal string values. */
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
/** Used to detect binary string values. */
var reIsBinary = /^0b[01]+$/i;
/** Used to detect host constructors (Safari). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used to detect octal string values. */
var reIsOctal = /^0o[0-7]+$/i;
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/** Used to compose unicode character classes. */
var rsAstralRange = "\\ud800-\\udfff",
rsComboMarksRange = "\\u0300-\\u036f",
reComboHalfMarksRange = "\\ufe20-\\ufe2f",
rsComboSymbolsRange = "\\u20d0-\\u20ff",
rsComboRange =
rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
rsVarRange = "\\ufe0e\\ufe0f";
/** Used to compose unicode capture groups. */
var rsAstral = "[" + rsAstralRange + "]",
rsCombo = "[" + rsComboRange + "]",
rsFitz = "\\ud83c[\\udffb-\\udfff]",
rsModifier = "(?:" + rsCombo + "|" + rsFitz + ")",
rsNonAstral = "[^" + rsAstralRange + "]",
rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}",
rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]",
rsZWJ = "\\u200d";
/** Used to compose unicode regexes. */
var reOptMod = rsModifier + "?",
rsOptVar = "[" + rsVarRange + "]?",
rsOptJoin =
"(?:" +
rsZWJ +
"(?:" +
[rsNonAstral, rsRegional, rsSurrPair].join("|") +
")" +
rsOptVar +
reOptMod +
")*",
rsSeq = rsOptVar + reOptMod + rsOptJoin,
rsSymbol =
"(?:" +
[
rsNonAstral + rsCombo + "?",
rsCombo,
rsRegional,
rsSurrPair,
rsAstral
].join("|") +
")";
/** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
var reUnicode = RegExp(
rsFitz + "(?=" + rsFitz + ")|" + rsSymbol + rsSeq,
"g"
);
/** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
var reHasUnicode = RegExp(
"[" + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + "]"
);
/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[
int8Tag
] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[
uint8Tag
] = typedArrayTags[uint8ClampedTag] = typedArrayTags[
uint16Tag
] = typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[
arrayBufferTag
] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[
dateTag
] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[
mapTag
] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[
regexpTag
] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[
weakMapTag
] = false;
/** Used to identify `toStringTag` values supported by `_.clone`. */
var cloneableTags = {};
cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[
arrayBufferTag
] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[
dateTag
] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[
int8Tag
] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[
mapTag
] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[
regexpTag
] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[
symbolTag
] = cloneableTags[uint8Tag] = cloneableTags[
uint8ClampedTag
] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[
weakMapTag
] = false;
/** Built-in method references without a dependency on `root`. */
var freeParseInt = parseInt;
/** Detect free variable `global` from Node.js. */
var freeGlobal =
typeof global == "object" &&
global &&
global.Object === Object &&
global;
/** Detect free variable `self`. */
var freeSelf =
typeof self == "object" && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function("return this")();
/** Detect free variable `exports`. */
var freeExports =
typeof exports == "object" && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule =
freeExports &&
typeof module == "object" &&
module &&
!module.nodeType &&
module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports && freeGlobal.process;
/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
try {
return (
freeProcess &&
freeProcess.binding &&
freeProcess.binding("util")
);
} catch (e) {}
})();
/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
/*--------------------------------------------------------------------------*/
/**
* Adds the key-value `pair` to `map`.
*
* @private
* @param {Object} map The map to modify.
* @param {Array} pair The key-value pair to add.
* @returns {Object} Returns `map`.
*/
function addMapEntry(map, pair) {
// Don't return `map.set` because it's not chainable in IE 11.
map.set(pair[0], pair[1]);
return map;
}
/**
* Adds `value` to `set`.
*
* @private
* @param {Object} set The set to modify.
* @param {*} value The value to add.
* @returns {Object} Returns `set`.
*/
function addSetEntry(set, value) {
// Don't return `set.add` because it's not chainable in IE 11.
set.add(value);
return set;
}
/**
* A faster alternative to `Function#apply`, this function invokes `func`
* with the `this` binding of `thisArg` and the arguments of `args`.
*
* @private
* @param {Function} func The function to invoke.
* @param {*} thisArg The `this` binding of `func`.
* @param {Array} args The arguments to invoke `func` with.
* @returns {*} Returns the result of `func`.
*/
function apply(func, thisArg, args) {
switch (args.length) {
case 0:
return func.call(thisArg);
case 1:
return func.call(thisArg, args[0]);
case 2:
return func.call(thisArg, args[0], args[1]);
case 3:
return func.call(thisArg, args[0], args[1], args[2]);
}
return func.apply(thisArg, args);
}
/**
* A specialized version of `_.forEach` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns `array`.
*/
function arrayEach(array, iteratee) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (iteratee(array[index], index, array) === false) {
break;
}
}
return array;
}
/**
* A specialized version of `_.filter` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {Array} Returns the new filtered array.
*/
function arrayFilter(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length,
resIndex = 0,
result = [];
while (++index < length) {
var value = array[index];
if (predicate(value, index, array)) {
result[resIndex++] = value;
}
}
return result;
}
/**
* A specialized version of `_.includes` for arrays without support for
* specifying an index to search from.
*
* @private
* @param {Array} [array] The array to inspect.
* @param {*} target The value to search for.
* @returns {boolean} Returns `true` if `target` is found, else `false`.
*/
function arrayIncludes(array, value) {
var length = array == null ? 0 : array.length;
return !!length && baseIndexOf(array, value, 0) > -1;
}
/**
* This function is like `arrayIncludes` except that it accepts a comparator.
*
* @private
* @param {Array} [array] The array to inspect.
* @param {*} target The value to search for.
* @param {Function} comparator The comparator invoked per element.
* @returns {boolean} Returns `true` if `target` is found, else `false`.
*/
function arrayIncludesWith(array, value, comparator) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (comparator(value, array[index])) {
return true;
}
}
return false;
}
/**
* A specialized version of `_.map` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the new mapped array.
*/
function arrayMap(array, iteratee) {
var index = -1,
length = array == null ? 0 : array.length,
result = Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
/**
* Appends the elements of `values` to `array`.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to append.
* @returns {Array} Returns `array`.
*/
function arrayPush(array, values) {
var index = -1,
length = values.length,
offset = array.length;
while (++index < length) {
array[offset + index] = values[index];
}
return array;
}
/**
* A specialized version of `_.reduce` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @param {*} [accumulator] The initial value.
* @param {boolean} [initAccum] Specify using the first element of `array` as
* the initial value.
* @returns {*} Returns the accumulated value.
*/
function arrayReduce(array, iteratee, accumulator, initAccum) {
var index = -1,
length = array == null ? 0 : array.length;
if (initAccum && length) {
accumulator = array[++index];
}
while (++index < length) {
accumulator = iteratee(accumulator, array[index], index, array);
}
return accumulator;
}
/**
* A specialized version of `_.some` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {boolean} Returns `true` if any element passes the predicate check,
* else `false`.
*/
function arraySome(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (predicate(array[index], index, array)) {
return true;
}
}
return false;
}
/**
* Converts an ASCII `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function asciiToArray(string) {
return string.split("");
}
/**
* The base implementation of `_.findIndex` and `_.findLastIndex` without
* support for iteratee shorthands.
*
* @private
* @param {Array} array The array to inspect.
* @param {Function} predicate The function invoked per iteration.
* @param {number} fromIndex The index to search from.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseFindIndex(array, predicate, fromIndex, fromRight) {
var length = array.length,
index = fromIndex + (fromRight ? 1 : -1);
while (fromRight ? index-- : ++index < length) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;
}
/**
* The base implementation of `_.indexOf` without `fromIndex` bounds checks.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseIndexOf(array, value, fromIndex) {
return value === value
? strictIndexOf(array, value, fromIndex)
: baseFindIndex(array, baseIsNaN, fromIndex);
}
/**
* The base implementation of `_.isNaN` without support for number objects.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
*/
function baseIsNaN(value) {
return value !== value;
}
/**
* The base implementation of `_.property` without support for deep paths.
*
* @private
* @param {string} key The key of the property to get.
* @returns {Function} Returns the new accessor function.
*/
function baseProperty(key) {
return function(object) {
return object == null ? undefined : object[key];
};
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/**
* The base implementation of `_.unary` without support for storing metadata.
*
* @private
* @param {Function} func The function to cap arguments for.
* @returns {Function} Returns the new capped function.
*/
function baseUnary(func) {
return function(value) {
return func(value);
};
}
/**
* The base implementation of `_.values` and `_.valuesIn` which creates an
* array of `object` property values corresponding to the property names
* of `props`.
*
* @private
* @param {Object} object The object to query.
* @param {Array} props The property names to get values for.
* @returns {Object} Returns the array of property values.
*/
function baseValues(object, props) {
return arrayMap(props, function(key) {
return object[key];
});
}
/**
* Checks if a `cache` value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function cacheHas(cache, key) {
return cache.has(key);
}
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function getValue(object, key) {
return object == null ? undefined : object[key];
}
/**
* Checks if `string` contains Unicode symbols.
*
* @private
* @param {string} string The string to inspect.
* @returns {boolean} Returns `true` if a symbol is found, else `false`.
*/
function hasUnicode(string) {
return reHasUnicode.test(string);
}
/**
* Converts `iterator` to an array.
*
* @private
* @param {Object} iterator The iterator to convert.
* @returns {Array} Returns the converted array.
*/
function iteratorToArray(iterator) {
var data,
result = [];
while (!(data = iterator.next()).done) {
result.push(data.value);
}
return result;
}
/**
* Converts `map` to its key-value pairs.
*
* @private
* @param {Object} map The map to convert.
* @returns {Array} Returns the key-value pairs.
*/
function mapToArray(map) {
var index = -1,
result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [key, value];
});
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/**
* Converts `set` to an array of its values.
*
* @private
* @param {Object} set The set to convert.
* @returns {Array} Returns the values.
*/
function setToArray(set) {
var index = -1,
result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
/**
* A specialized version of `_.indexOf` which performs strict equality
* comparisons of values, i.e. `===`.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function strictIndexOf(array, value, fromIndex) {
var index = fromIndex - 1,
length = array.length;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/**
* Converts `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function stringToArray(string) {
return hasUnicode(string)
? unicodeToArray(string)
: asciiToArray(string);
}
/**
* Converts a Unicode `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function unicodeToArray(string) {
return string.match(reUnicode) || [];
}
/*--------------------------------------------------------------------------*/
/** Used for built-in method references. */
var arrayProto = Array.prototype,
funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to detect overreaching core-js shims. */
var coreJsData = root["__core-js_shared__"];
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to detect methods masquerading as native. */
var maskSrcKey = (function() {
var uid = /[^.]+$/.exec(
(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO) || ""
);
return uid ? "Symbol(src)_1." + uid : "";
})();
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;
/** Used to infer the `Object` constructor. */
var objectCtorString = funcToString.call(Object);
/** Used to detect if a method is native. */
var reIsNative = RegExp(
"^" +
funcToString
.call(hasOwnProperty)
.replace(reRegExpChar, "\\$&")
.replace(
/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,
"$1.*?"
) +
"$"
);
/** Built-in value references. */
var Buffer = moduleExports ? root.Buffer : undefined,
Symbol = root.Symbol,
Uint8Array = root.Uint8Array,
allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
getPrototype = overArg(Object.getPrototypeOf, Object),
objectCreate = Object.create,
propertyIsEnumerable = objectProto.propertyIsEnumerable,
splice = arrayProto.splice,
spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
symIterator = Symbol ? Symbol.iterator : undefined,
symToStringTag = Symbol ? Symbol.toStringTag : undefined;
var defineProperty = (function() {
try {
var func = getNative(Object, "defineProperty");
func({}, "", {});
return func;
} catch (e) {}
})();
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeGetSymbols = Object.getOwnPropertySymbols,
nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
nativeKeys = overArg(Object.keys, Object),
nativeMax = Math.max,
nativeNow = Date.now;
/* Built-in method references that are verified to be native. */
var DataView = getNative(root, "DataView"),
Map = getNative(root, "Map"),
Promise = getNative(root, "Promise"),
Set = getNative(root, "Set"),
WeakMap = getNative(root, "WeakMap"),
nativeCreate = getNative(Object, "create");
/** Used to lookup unminified function names. */
var realNames = {};
/** Used to detect maps, sets, and weakmaps. */
var dataViewCtorString = toSource(DataView),
mapCtorString = toSource(Map),
promiseCtorString = toSource(Promise),
setCtorString = toSource(Set),
weakMapCtorString = toSource(WeakMap);
/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol ? Symbol.prototype : undefined,
symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
symbolToString = symbolProto ? symbolProto.toString : undefined;
/*------------------------------------------------------------------------*/
/**
* Creates a `lodash` object which wraps `value` to enable implicit method
* chain sequences. Methods that operate on and return arrays, collections,
* and functions can be chained together. Methods that retrieve a single value
* or may return a primitive value will automatically end the chain sequence
* and return the unwrapped value. Otherwise, the value must be unwrapped
* with `_#value`.
*
* Explicit chain sequences, which must be unwrapped with `_#value`, may be
* enabled using `_.chain`.
*
* The execution of chained methods is lazy, that is, it's deferred until
* `_#value` is implicitly or explicitly called.
*
* Lazy evaluation allows several methods to support shortcut fusion.
* Shortcut fusion is an optimization to merge iteratee calls; this avoids
* the creation of intermediate arrays and can greatly reduce the number of
* iteratee executions. Sections of a chain sequence qualify for shortcut
* fusion if the section is applied to an array and iteratees accept only
* one argument. The heuristic for whether a section qualifies for shortcut
* fusion is subject to change.
*
* Chaining is supported in custom builds as long as the `_#value` method is
* directly or indirectly included in the build.
*
* In addition to lodash methods, wrappers have `Array` and `String` methods.
*
* The wrapper `Array` methods are:
* `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
*
* The wrapper `String` methods are:
* `replace` and `split`
*
* The wrapper methods that support shortcut fusion are:
* `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
* `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
* `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
*
* The chainable wrapper methods are:
* `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
* `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
* `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
* `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
* `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
* `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
* `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
* `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
* `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
* `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
* `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
* `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
* `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
* `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
* `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
* `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
* `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
* `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
* `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
* `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
* `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
* `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
* `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
* `zipObject`, `zipObjectDeep`, and `zipWith`
*
* The wrapper methods that are **not** chainable by default are:
* `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
* `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
* `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
* `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
* `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
* `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
* `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
* `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
* `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
* `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
* `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
* `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
* `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
* `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
* `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
* `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
* `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
* `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
* `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
* `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
* `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
* `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
* `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
* `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
* `upperFirst`, `value`, and `words`
*
* @name _
* @constructor
* @category Seq
* @param {*} value The value to wrap in a `lodash` instance.
* @returns {Object} Returns the new `lodash` wrapper instance.
* @example
*
* function square(n) {
* return n * n;
* }
*
* var wrapped = _([1, 2, 3]);
*
* // Returns an unwrapped value.
* wrapped.reduce(_.add);
* // => 6
*
* // Returns a wrapped value.
* var squares = wrapped.map(square);
*
* _.isArray(squares);
* // => false
*
* _.isArray(squares.value());
* // => true
*/
function lodash() {
// No operation performed.
}
/**
* The base implementation of `_.create` without support for assigning
* properties to the created object.
*
* @private
* @param {Object} proto The object to inherit from.
* @returns {Object} Returns the new object.
*/
var baseCreate = (function() {
function object() {}
return function(proto) {
if (!isObject(proto)) {
return {};
}
if (objectCreate) {
return objectCreate(proto);
}
object.prototype = proto;
var result = new object();
object.prototype = undefined;
return result;
};
})();
/*------------------------------------------------------------------------*/
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Hash(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/
function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
this.size = 0;
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function hashDelete(key) {
var result = this.has(key) && delete this.__data__[key];
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function hashGet(key) {
var data = this.__data__;
if (nativeCreate) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty.call(data, key) ? data[key] : undefined;
}
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function hashHas(key) {
var data = this.__data__;
return nativeCreate
? data[key] !== undefined
: hasOwnProperty.call(data, key);
}
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/
function hashSet(key, value) {
var data = this.__data__;
this.size += this.has(key) ? 0 : 1;
data[key] =
nativeCreate && value === undefined ? HASH_UNDEFINED : value;
return this;
}
// Add methods to `Hash`.
Hash.prototype.clear = hashClear;
Hash.prototype["delete"] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/*------------------------------------------------------------------------*/
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/
function listCacheClear() {
this.__data__ = [];
this.size = 0;
}
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
return false;
}
var lastIndex = data.length - 1;
if (index == lastIndex) {
data.pop();
} else {
splice.call(data, index, 1);
}
--this.size;
return true;
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
++this.size;
data.push([key, value]);
} else {
data[index][1] = value;
}
return this;
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype["delete"] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/*------------------------------------------------------------------------*/
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function MapCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/
function mapCacheClear() {
this.size = 0;
this.__data__ = {
hash: new Hash(),
map: new (Map || ListCache)(),
string: new Hash()
};
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function mapCacheDelete(key) {
var result = getMapData(this, key)["delete"](key);
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function mapCacheGet(key) {
return getMapData(this, key).get(key);
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function mapCacheHas(key) {
return getMapData(this, key).has(key);
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/
function mapCacheSet(key, value) {
var data = getMapData(this, key),
size = data.size;
data.set(key, value);
this.size += data.size == size ? 0 : 1;
return this;
}
// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype["delete"] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;
/*------------------------------------------------------------------------*/
/**
*
* Creates an array cache object to store unique values.
*
* @private
* @constructor
* @param {Array} [values] The values to cache.
*/
function SetCache(values) {
var index = -1,
length = values == null ? 0 : values.length;
this.__data__ = new MapCache();
while (++index < length) {
this.add(values[index]);
}
}
/**
* Adds `value` to the array cache.
*
* @private
* @name add
* @memberOf SetCache
* @alias push
* @param {*} value The value to cache.
* @returns {Object} Returns the cache instance.
*/
function setCacheAdd(value) {
this.__data__.set(value, HASH_UNDEFINED);
return this;
}
/**
* Checks if `value` is in the array cache.
*
* @private
* @name has
* @memberOf SetCache
* @param {*} value The value to search for.
* @returns {number} Returns `true` if `value` is found, else `false`.
*/
function setCacheHas(value) {
return this.__data__.has(value);
}
// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;
/*------------------------------------------------------------------------*/
/**
* Creates a stack cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Stack(entries) {
var data = (this.__data__ = new ListCache(entries));
this.size = data.size;
}
/**
* Removes all key-value entries from the stack.
*
* @private
* @name clear
* @memberOf Stack
*/
function stackClear() {
this.__data__ = new ListCache();
this.size = 0;
}
/**
* Removes `key` and its value from the stack.
*
* @private
* @name delete
* @memberOf Stack
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function stackDelete(key) {
var data = this.__data__,
result = data["delete"](key);
this.size = data.size;
return result;
}
/**
* Gets the stack value for `key`.
*
* @private
* @name get
* @memberOf Stack
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function stackGet(key) {
return this.__data__.get(key);
}
/**
* Checks if a stack value for `key` exists.
*
* @private
* @name has
* @memberOf Stack
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function stackHas(key) {
return this.__data__.has(key);
}
/**
* Sets the stack `key` to `value`.
*
* @private
* @name set
* @memberOf Stack
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the stack cache instance.
*/
function stackSet(key, value) {
var data = this.__data__;
if (data instanceof ListCache) {
var pairs = data.__data__;
if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
pairs.push([key, value]);
this.size = ++data.size;
return this;
}
data = this.__data__ = new MapCache(pairs);
}
data.set(key, value);
this.size = data.size;
return this;
}
// Add methods to `Stack`.
Stack.prototype.clear = stackClear;
Stack.prototype["delete"] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;
/*------------------------------------------------------------------------*/
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys(value, inherited) {
var isArr = isArray(value),
isArg = !isArr && isArguments(value),
isBuff = !isArr && !isArg && isBuffer(value),
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
skipIndexes = isArr || isArg || isBuff || isType,
result = skipIndexes ? baseTimes(value.length, String) : [],
length = result.length;
for (var key in value) {
if (
(inherited || hasOwnProperty.call(value, key)) &&
!(
skipIndexes &&
// Safari 9 has enumerable `arguments.length` in strict mode.
(key == "length" ||
// Node.js 0.10 has enumerable non-index properties on buffers.
(isBuff && (key == "offset" || key == "parent")) ||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
(isType &&
(key == "buffer" ||
key == "byteLength" ||
key == "byteOffset")) ||
// Skip index properties.
isIndex(key, length))
)
) {
result.push(key);
}
}
return result;
}
/**
* This function is like `assignValue` except that it doesn't assign
* `undefined` values.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignMergeValue(object, key, value) {
if (
(value !== undefined && !eq(object[key], value)) ||
(value === undefined && !(key in object))
) {
baseAssignValue(object, key, value);
}
}
/**
* Assigns `value` to `key` of `object` if the existing value is not equivalent
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignValue(object, key, value) {
var objValue = object[key];
if (
!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))
) {
baseAssignValue(object, key, value);
}
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key) {
var length = array.length;
while (length--) {
if (eq(array[length][0], key)) {
return length;
}
}
return -1;
}
/**
* The base implementation of `_.assign` without support for multiple sources
* or `customizer` functions.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @returns {Object} Returns `object`.
*/
function baseAssign(object, source) {
return object && copyObject(source, keys(source), object);
}
/**
* The base implementation of `_.assignIn` without support for multiple sources
* or `customizer` functions.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @returns {Object} Returns `object`.
*/
function baseAssignIn(object, source) {
return object && copyObject(source, keysIn(source), object);
}
/**
* The base implementation of `assignValue` and `assignMergeValue` without
* value checks.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function baseAssignValue(object, key, value) {
if (key == "__proto__" && defineProperty) {
defineProperty(object, key, {
configurable: true,
enumerable: true,
value: value,
writable: true
});
} else {
object[key] = value;
}
}
/**
* The base implementation of `_.clone` and `_.cloneDeep` which tracks
* traversed objects.
*
* @private
* @param {*} value The value to clone.
* @param {boolean} bitmask The bitmask flags.
* 1 - Deep clone
* 2 - Flatten inherited properties
* 4 - Clone symbols
* @param {Function} [customizer] The function to customize cloning.
* @param {string} [key] The key of `value`.
* @param {Object} [object] The parent object of `value`.
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
* @returns {*} Returns the cloned value.
*/
function baseClone(value, bitmask, customizer, key, object, stack) {
var result,
isDeep = bitmask & CLONE_DEEP_FLAG,
isFlat = bitmask & CLONE_FLAT_FLAG,
isFull = bitmask & CLONE_SYMBOLS_FLAG;
if (customizer) {
result = object
? customizer(value, key, object, stack)
: customizer(value);
}
if (result !== undefined) {
return result;
}
if (!isObject(value)) {
return value;
}
var isArr = isArray(value);
if (isArr) {
result = initCloneArray(value);
if (!isDeep) {
return copyArray(value, result);
}
} else {
var tag = getTag(value),
isFunc = tag == funcTag || tag == genTag;
if (isBuffer(value)) {
return cloneBuffer(value, isDeep);
}
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = isFlat || isFunc ? {} : initCloneObject(value);
if (!isDeep) {
return isFlat
? copySymbolsIn(value, baseAssignIn(result, value))
: copySymbols(value, baseAssign(result, value));
}
} else {
if (!cloneableTags[tag]) {
return object ? value : {};
}
result = initCloneByTag(value, tag, baseClone, isDeep);
}
}
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack());
var stacked = stack.get(value);
if (stacked) {
return stacked;
}
stack.set(value, result);
var keysFunc = isFull
? isFlat
? getAllKeysIn
: getAllKeys
: isFlat
? keysIn
: keys;
var props = isArr ? undefined : keysFunc(value);
arrayEach(props || value, function(subValue, key) {
if (props) {
key = subValue;
subValue = value[key];
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(
result,
key,
baseClone(subValue, bitmask, customizer, key, value, stack)
);
});
return result;
}
/**
* The base implementation of `_.forEach` without support for iteratee shorthands.
*
* @private
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array|Object} Returns `collection`.
*/
var baseEach = createBaseEach(baseForOwn);
/**
* The base implementation of `_.flatten` with support for restricting flattening.
*
* @private
* @param {Array} array The array to flatten.
* @param {number} depth The maximum recursion depth.
* @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
* @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
* @param {Array} [result=[]] The initial result value.
* @returns {Array} Returns the new flattened array.
*/
function baseFlatten(array, depth, predicate, isStrict, result) {
var index = -1,
length = array.length;
predicate || (predicate = isFlattenable);
result || (result = []);
while (++index < length) {
var value = array[index];
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result);
} else {
arrayPush(result, value);
}
} else if (!isStrict) {
result[result.length] = value;
}
}
return result;
}
/**
* The base implementation of `baseForOwn` which iterates over `object`
* properties returned by `keysFunc` and invokes `iteratee` for each property.
* Iteratee functions may exit iteration early by explicitly returning `false`.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Object} Returns `object`.
*/
var baseFor = createBaseFor();
/**
* The base implementation of `_.forOwn` without support for iteratee shorthands.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Object} Returns `object`.
*/
function baseForOwn(object, iteratee) {
return object && baseFor(object, iteratee, keys);
}
/**
* The base implementation of `_.get` without support for default values.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @returns {*} Returns the resolved value.
*/
function baseGet(object, path) {
path = castPath(path, object);
var index = 0,
length = path.length;
while (object != null && index < length) {
object = object[toKey(path[index++])];
}
return index && index == length ? object : undefined;
}
/**
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Function} keysFunc The function to get the keys of `object`.
* @param {Function} symbolsFunc The function to get the symbols of `object`.
* @returns {Array} Returns the array of property names and symbols.
*/
function baseGetAllKeys(object, keysFunc, symbolsFunc) {
var result = keysFunc(object);
return isArray(object)
? result
: arrayPush(result, symbolsFunc(object));
}
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return symToStringTag && symToStringTag in Object(value)
? getRawTag(value)
: objectToString(value);
}
/**
* The base implementation of `_.hasIn` without support for deep paths.
*
* @private
* @param {Object} [object] The object to query.
* @param {Array|string} key The key to check.
* @returns {boolean} Returns `true` if `key` exists, else `false`.
*/
function baseHasIn(object, key) {
return object != null && key in Object(object);
}
/**
* The base implementation of `_.isArguments`.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
*/
function baseIsArguments(value) {
return isObjectLike(value) && baseGetTag(value) == argsTag;
}
/**
* The base implementation of `_.isEqual` which supports partial comparisons
* and tracks traversed objects.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {boolean} bitmask The bitmask flags.
* 1 - Unordered comparison
* 2 - Partial comparison
* @param {Function} [customizer] The function to customize comparisons.
* @param {Object} [stack] Tracks traversed `value` and `other` objects.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/
function baseIsEqual(value, other, bitmask, customizer, stack) {
if (value === other) {
return true;
}
if (
value == null ||
other == null ||
(!isObjectLike(value) && !isObjectLike(other))
) {
return value !== value && other !== other;
}
return baseIsEqualDeep(
value,
other,
bitmask,
customizer,
baseIsEqual,
stack
);
}
/**
* A specialized version of `baseIsEqual` for arrays and objects which performs
* deep comparisons and tracks traversed objects enabling objects with circular
* references to be compared.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} [stack] Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function baseIsEqualDeep(
object,
other,
bitmask,
customizer,
equalFunc,
stack
) {
var objIsArr = isArray(object),
othIsArr = isArray(other),
objTag = objIsArr ? arrayTag : getTag(object),
othTag = othIsArr ? arrayTag : getTag(other);
objTag = objTag == argsTag ? objectTag : objTag;
othTag = othTag == argsTag ? objectTag : othTag;
var objIsObj = objTag == objectTag,
othIsObj = othTag == objectTag,
isSameTag = objTag == othTag;
if (isSameTag && isBuffer(object)) {
if (!isBuffer(other)) {
return false;
}
objIsArr = true;
objIsObj = false;
}
if (isSameTag && !objIsObj) {
stack || (stack = new Stack());
return objIsArr || isTypedArray(object)
? equalArrays(
object,
other,
bitmask,
customizer,
equalFunc,
stack
)
: equalByTag(
object,
other,
objTag,
bitmask,
customizer,
equalFunc,
stack
);
}
if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
var objIsWrapped =
objIsObj && hasOwnProperty.call(object, "__wrapped__"),
othIsWrapped =
othIsObj && hasOwnProperty.call(other, "__wrapped__");
if (objIsWrapped || othIsWrapped) {
var objUnwrapped = objIsWrapped ? object.value() : object,
othUnwrapped = othIsWrapped ? other.value() : other;
stack || (stack = new Stack());
return equalFunc(
objUnwrapped,
othUnwrapped,
bitmask,
customizer,
stack
);
}
}
if (!isSameTag) {
return false;
}
stack || (stack = new Stack());
return equalObjects(
object,
other,
bitmask,
customizer,
equalFunc,
stack
);
}
/**
* The base implementation of `_.isMatch` without support for iteratee shorthands.
*
* @private
* @param {Object} object The object to inspect.
* @param {Object} source The object of property values to match.
* @param {Array} matchData The property names, values, and compare flags to match.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
*/
function baseIsMatch(object, source, matchData, customizer) {
var index = matchData.length,
length = index,
noCustomizer = !customizer;
if (object == null) {
return !length;
}
object = Object(object);
while (index--) {
var data = matchData[index];
if (
noCustomizer && data[2]
? data[1] !== object[data[0]]
: !(data[0] in object)
) {
return false;
}
}
while (++index < length) {
data = matchData[index];
var key = data[0],
objValue = object[key],
srcValue = data[1];
if (noCustomizer && data[2]) {
if (objValue === undefined && !(key in object)) {
return false;
}
} else {
var stack = new Stack();
if (customizer) {
var result = customizer(
objValue,
srcValue,
key,
object,
source,
stack
);
}
if (
!(result === undefined
? baseIsEqual(
srcValue,
objValue,
COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG,
customizer,
stack
)
: result)
) {
return false;
}
}
}
return true;
}
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/
function baseIsNative(value) {
if (!isObject(value) || isMasked(value)) {
return false;
}
var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
return pattern.test(toSource(value));
}
/**
* The base implementation of `_.isTypedArray` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
*/
function baseIsTypedArray(value) {
return (
isObjectLike(value) &&
isLength(value.length) &&
!!typedArrayTags[baseGetTag(value)]
);
}
/**
* The base implementation of `_.iteratee`.
*
* @private
* @param {*} [value=_.identity] The value to convert to an iteratee.
* @returns {Function} Returns the iteratee.
*/
function baseIteratee(value) {
// Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
// See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
if (typeof value == "function") {
return value;
}
if (value == null) {
return identity;
}
if (typeof value == "object") {
return isArray(value)
? baseMatchesProperty(value[0], value[1])
: baseMatches(value);
}
return property(value);
}
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeys(object) {
if (!isPrototype(object)) {
return nativeKeys(object);
}
var result = [];
for (var key in Object(object)) {
if (hasOwnProperty.call(object, key) && key != "constructor") {
result.push(key);
}
}
return result;
}
/**
* The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeysIn(object) {
if (!isObject(object)) {
return nativeKeysIn(object);
}
var isProto = isPrototype(object),
result = [];
for (var key in object) {
if (
!(
key == "constructor" &&
(isProto || !hasOwnProperty.call(object, key))
)
) {
result.push(key);
}
}
return result;
}
/**
* The base implementation of `_.matches` which doesn't clone `source`.
*
* @private
* @param {Object} source The object of property values to match.
* @returns {Function} Returns the new spec function.
*/
function baseMatches(source) {
var matchData = getMatchData(source);
if (matchData.length == 1 && matchData[0][2]) {
return matchesStrictComparable(matchData[0][0], matchData[0][1]);
}
return function(object) {
return object === source || baseIsMatch(object, source, matchData);
};
}
/**
* The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
*
* @private
* @param {string} path The path of the property to get.
* @param {*} srcValue The value to match.
* @returns {Function} Returns the new spec function.
*/
function baseMatchesProperty(path, srcValue) {
if (isKey(path) && isStrictComparable(srcValue)) {
return matchesStrictComparable(toKey(path), srcValue);
}
return function(object) {
var objValue = get(object, path);
return objValue === undefined && objValue === srcValue
? hasIn(object, path)
: baseIsEqual(
srcValue,
objValue,
COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG
);
};
}
/**
* The base implementation of `_.merge` without support for multiple sources.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @param {number} srcIndex The index of `source`.
* @param {Function} [customizer] The function to customize merged values.
* @param {Object} [stack] Tracks traversed source values and their merged
* counterparts.
*/
function baseMerge(object, source, srcIndex, customizer, stack) {
if (object === source) {
return;
}
baseFor(
source,
function(srcValue, key) {
if (isObject(srcValue)) {
stack || (stack = new Stack());
baseMergeDeep(
object,
source,
key,
srcIndex,
baseMerge,
customizer,
stack
);
} else {
var newValue = customizer
? customizer(
object[key],
srcValue,
key + "",
object,
source,
stack
)
: undefined;
if (newValue === undefined) {
newValue = srcValue;
}
assignMergeValue(object, key, newValue);
}
},
keysIn
);
}
/**
* A specialized version of `baseMerge` for arrays and objects which performs
* deep merges and tracks traversed objects enabling objects with circular
* references to be merged.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @param {string} key The key of the value to merge.
* @param {number} srcIndex The index of `source`.
* @param {Function} mergeFunc The function to merge values.
* @param {Function} [customizer] The function to customize assigned values.
* @param {Object} [stack] Tracks traversed source values and their merged
* counterparts.
*/
function baseMergeDeep(
object,
source,
key,
srcIndex,
mergeFunc,
customizer,
stack
) {
var objValue = object[key],
srcValue = source[key],
stacked = stack.get(srcValue);
if (stacked) {
assignMergeValue(object, key, stacked);
return;
}
var newValue = customizer
? customizer(objValue, srcValue, key + "", object, source, stack)
: undefined;
var isCommon = newValue === undefined;
if (isCommon) {
var isArr = isArray(srcValue),
isBuff = !isArr && isBuffer(srcValue),
isTyped = !isArr && !isBuff && isTypedArray(srcValue);
newValue = srcValue;
if (isArr || isBuff || isTyped) {
if (isArray(objValue)) {
newValue = objValue;
} else if (isArrayLikeObject(objValue)) {
newValue = copyArray(objValue);
} else if (isBuff) {
isCommon = false;
newValue = cloneBuffer(srcValue, true);
} else if (isTyped) {
isCommon = false;
newValue = cloneTypedArray(srcValue, true);
} else {
newValue = [];
}
} else if (isPlainObject(srcValue) || isArguments(srcValue)) {
newValue = objValue;
if (isArguments(objValue)) {
newValue = toPlainObject(objValue);
} else if (
!isObject(objValue) ||
(srcIndex && isFunction(objValue))
) {
newValue = initCloneObject(srcValue);
}
} else {
isCommon = false;
}
}
if (isCommon) {
// Recursively merge objects and arrays (susceptible to call stack limits).
stack.set(srcValue, newValue);
mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
stack["delete"](srcValue);
}
assignMergeValue(object, key, newValue);
}
/**
* A specialized version of `baseProperty` which supports deep paths.
*
* @private
* @param {Array|string} path The path of the property to get.
* @returns {Function} Returns the new accessor function.
*/
function basePropertyDeep(path) {
return function(object) {
return baseGet(object, path);
};
}
/**
* The base implementation of `_.rest` which doesn't validate or coerce arguments.
*
* @private
* @param {Function} func The function to apply a rest parameter to.
* @param {number} [start=func.length-1] The start position of the rest parameter.
* @returns {Function} Returns the new function.
*/
function baseRest(func, start) {
return setToString(overRest(func, start, identity), func + "");
}
/**
* The base implementation of `_.set`.
*
* @private
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @param {Function} [customizer] The function to customize path creation.
* @returns {Object} Returns `object`.
*/
function baseSet(object, path, value, customizer) {
if (!isObject(object)) {
return object;
}
path = castPath(path, object);
var index = -1,
length = path.length,
lastIndex = length - 1,
nested = object;
while (nested != null && ++index < length) {
var key = toKey(path[index]),
newValue = value;
if (index != lastIndex) {
var objValue = nested[key];
newValue = customizer
? customizer(objValue, key, nested)
: undefined;
if (newValue === undefined) {
newValue = isObject(objValue)
? objValue
: isIndex(path[index + 1])
? []
: {};
}
}
assignValue(nested, key, newValue);
nested = nested[key];
}
return object;
}
/**
* The base implementation of `setToString` without support for hot loop shorting.
*
* @private
* @param {Function} func The function to modify.
* @param {Function} string The `toString` result.
* @returns {Function} Returns `func`.
*/
var baseSetToString = !defineProperty
? identity
: function(func, string) {
return defineProperty(func, "toString", {
configurable: true,
enumerable: false,
value: constant(string),
writable: true
});
};
/**
* The base implementation of `_.toString` which doesn't convert nullish
* values to empty strings.
*
* @private
* @param {*} value The value to process.
* @returns {string} Returns the string.
*/
function baseToString(value) {
// Exit early for strings to avoid a performance hit in some environments.
if (typeof value == "string") {
return value;
}
if (isArray(value)) {
// Recursively convert values (susceptible to call stack limits).
return arrayMap(value, baseToString) + "";
}
if (isSymbol(value)) {
return symbolToString ? symbolToString.call(value) : "";
}
var result = value + "";
return result == "0" && 1 / value == -INFINITY ? "-0" : result;
}
/**
* The base implementation of `_.uniqBy` without support for iteratee shorthands.
*
* @private
* @param {Array} array The array to inspect.
* @param {Function} [iteratee] The iteratee invoked per element.
* @param {Function} [comparator] The comparator invoked per element.
* @returns {Array} Returns the new duplicate free array.
*/
function baseUniq(array, iteratee, comparator) {
var index = -1,
includes = arrayIncludes,
length = array.length,
isCommon = true,
result = [],
seen = result;
if (comparator) {
isCommon = false;
includes = arrayIncludesWith;
} else if (length >= LARGE_ARRAY_SIZE) {
var set = iteratee ? null : createSet(array);
if (set) {
return setToArray(set);
}
isCommon = false;
includes = cacheHas;
seen = new SetCache();
} else {
seen = iteratee ? [] : result;
}
outer: while (++index < length) {
var value = array[index],
computed = iteratee ? iteratee(value) : value;
value = comparator || value !== 0 ? value : 0;
if (isCommon && computed === computed) {
var seenIndex = seen.length;
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer;
}
}
if (iteratee) {
seen.push(computed);
}
result.push(value);
} else if (!includes(seen, computed, comparator)) {
if (seen !== result) {
seen.push(computed);
}
result.push(value);
}
}
return result;
}
/**
* Casts `value` to a path array if it's not one.
*
* @private
* @param {*} value The value to inspect.
* @param {Object} [object] The object to query keys on.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value, object) {
if (isArray(value)) {
return value;
}
return isKey(value, object) ? [value] : stringToPath(toString(value));
}
/**
* Creates a clone of `buffer`.
*
* @private
* @param {Buffer} buffer The buffer to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Buffer} Returns the cloned buffer.
*/
function cloneBuffer(buffer, isDeep) {
if (isDeep) {
return buffer.slice();
}
var length = buffer.length,
result = allocUnsafe
? allocUnsafe(length)
: new buffer.constructor(length);
buffer.copy(result);
return result;
}
/**
* Creates a clone of `arrayBuffer`.
*
* @private
* @param {ArrayBuffer} arrayBuffer The array buffer to clone.
* @returns {ArrayBuffer} Returns the cloned array buffer.
*/
function cloneArrayBuffer(arrayBuffer) {
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
return result;
}
/**
* Creates a clone of `dataView`.
*
* @private
* @param {Object} dataView The data view to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned data view.
*/
function cloneDataView(dataView, isDeep) {
var buffer = isDeep
? cloneArrayBuffer(dataView.buffer)
: dataView.buffer;
return new dataView.constructor(
buffer,
dataView.byteOffset,
dataView.byteLength
);
}
/**
* Creates a clone of `map`.
*
* @private
* @param {Object} map The map to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned map.
*/
function cloneMap(map, isDeep, cloneFunc) {
var array = isDeep
? cloneFunc(mapToArray(map), CLONE_DEEP_FLAG)
: mapToArray(map);
return arrayReduce(array, addMapEntry, new map.constructor());
}
/**
* Creates a clone of `regexp`.
*
* @private
* @param {Object} regexp The regexp to clone.
* @returns {Object} Returns the cloned regexp.
*/
function cloneRegExp(regexp) {
var result = new regexp.constructor(
regexp.source,
reFlags.exec(regexp)
);
result.lastIndex = regexp.lastIndex;
return result;
}
/**
* Creates a clone of `set`.
*
* @private
* @param {Object} set The set to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned set.
*/
function cloneSet(set, isDeep, cloneFunc) {
var array = isDeep
? cloneFunc(setToArray(set), CLONE_DEEP_FLAG)
: setToArray(set);
return arrayReduce(array, addSetEntry, new set.constructor());
}
/**
* Creates a clone of the `symbol` object.
*
* @private
* @param {Object} symbol The symbol object to clone.
* @returns {Object} Returns the cloned symbol object.
*/
function cloneSymbol(symbol) {
return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
}
/**
* Creates a clone of `typedArray`.
*
* @private
* @param {Object} typedArray The typed array to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned typed array.
*/
function cloneTypedArray(typedArray, isDeep) {
var buffer = isDeep
? cloneArrayBuffer(typedArray.buffer)
: typedArray.buffer;
return new typedArray.constructor(
buffer,
typedArray.byteOffset,
typedArray.length
);
}
/**
* Copies the values of `source` to `array`.
*
* @private
* @param {Array} source The array to copy values from.
* @param {Array} [array=[]] The array to copy values to.
* @returns {Array} Returns `array`.
*/
function copyArray(source, array) {
var index = -1,
length = source.length;
array || (array = Array(length));
while (++index < length) {
array[index] = source[index];
}
return array;
}
/**
* Copies properties of `source` to `object`.
*
* @private
* @param {Object} source The object to copy properties from.
* @param {Array} props The property identifiers to copy.
* @param {Object} [object={}] The object to copy properties to.
* @param {Function} [customizer] The function to customize copied values.
* @returns {Object} Returns `object`.
*/
function copyObject(source, props, object, customizer) {
var isNew = !object;
object || (object = {});
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
if (newValue === undefined) {
newValue = source[key];
}
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
}
return object;
}
/**
* Copies own symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbols(source, object) {
return copyObject(source, getSymbols(source), object);
}
/**
* Copies own and inherited symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbolsIn(source, object) {
return copyObject(source, getSymbolsIn(source), object);
}
/**
* Creates a function like `_.assign`.
*
* @private
* @param {Function} assigner The function to assign values.
* @returns {Function} Returns the new assigner function.
*/
function createAssigner(assigner) {
return baseRest(function(object, sources) {
var index = -1,
length = sources.length,
customizer = length > 1 ? sources[length - 1] : undefined,
guard = length > 2 ? sources[2] : undefined;
customizer =
assigner.length > 3 && typeof customizer == "function"
? (length--, customizer)
: undefined;
if (guard && isIterateeCall(sources[0], sources[1], guard)) {
customizer = length < 3 ? undefined : customizer;
length = 1;
}
object = Object(object);
while (++index < length) {
var source = sources[index];
if (source) {
assigner(object, source, index, customizer);
}
}
return object;
});
}
/**
* Creates a `baseEach` or `baseEachRight` function.
*
* @private
* @param {Function} eachFunc The function to iterate over a collection.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new base function.
*/
function createBaseEach(eachFunc, fromRight) {
return function(collection, iteratee) {
if (collection == null) {
return collection;
}
if (!isArrayLike(collection)) {
return eachFunc(collection, iteratee);
}
var length = collection.length,
index = fromRight ? length : -1,
iterable = Object(collection);
while (fromRight ? index-- : ++index < length) {
if (iteratee(iterable[index], index, iterable) === false) {
break;
}
}
return collection;
};
}
/**
* Creates a base function for methods like `_.forIn` and `_.forOwn`.
*
* @private
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new base function.
*/
function createBaseFor(fromRight) {
return function(object, iteratee, keysFunc) {
var index = -1,
iterable = Object(object),
props = keysFunc(object),
length = props.length;
while (length--) {
var key = props[fromRight ? length : ++index];
if (iteratee(iterable[key], key, iterable) === false) {
break;
}
}
return object;
};
}
/**
* Creates a set object of `values`.
*
* @private
* @param {Array} values The values to add to the set.
* @returns {Object} Returns the new set.
*/
var createSet = !(Set && 1 / setToArray(new Set([, -0]))[1] == INFINITY)
? noop
: function(values) {
return new Set(values);
};
/**
* A specialized version of `baseIsEqualDeep` for arrays with support for
* partial deep comparisons.
*
* @private
* @param {Array} array The array to compare.
* @param {Array} other The other array to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `array` and `other` objects.
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/
function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
arrLength = array.length,
othLength = other.length;
if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(array);
if (stacked && stack.get(other)) {
return stacked == other;
}
var index = -1,
result = true,
seen =
bitmask & COMPARE_UNORDERED_FLAG ? new SetCache() : undefined;
stack.set(array, other);
stack.set(other, array);
// Ignore non-index properties.
while (++index < arrLength) {
var arrValue = array[index],
othValue = other[index];
if (customizer) {
var compared = isPartial
? customizer(othValue, arrValue, index, other, array, stack)
: customizer(
arrValue,
othValue,
index,
array,
other,
stack
);
}
if (compared !== undefined) {
if (compared) {
continue;
}
result = false;
break;
}
// Recursively compare arrays (susceptible to call stack limits).
if (seen) {
if (
!arraySome(other, function(othValue, othIndex) {
if (
!cacheHas(seen, othIndex) &&
(arrValue === othValue ||
equalFunc(
arrValue,
othValue,
bitmask,
customizer,
stack
))
) {
return seen.push(othIndex);
}
})
) {
result = false;
break;
}
} else if (
!(
arrValue === othValue ||
equalFunc(arrValue, othValue, bitmask, customizer, stack)
)
) {
result = false;
break;
}
}
stack["delete"](array);
stack["delete"](other);
return result;
}
/**
* A specialized version of `baseIsEqualDeep` for comparing objects of
* the same `toStringTag`.
*
* **Note:** This function only supports comparing values with tags of
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {string} tag The `toStringTag` of the objects to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function equalByTag(
object,
other,
tag,
bitmask,
customizer,
equalFunc,
stack
) {
switch (tag) {
case dataViewTag:
if (
object.byteLength != other.byteLength ||
object.byteOffset != other.byteOffset
) {
return false;
}
object = object.buffer;
other = other.buffer;
case arrayBufferTag:
if (
object.byteLength != other.byteLength ||
!equalFunc(new Uint8Array(object), new Uint8Array(other))
) {
return false;
}
return true;
case boolTag:
case dateTag:
case numberTag:
// Coerce booleans to `1` or `0` and dates to milliseconds.
// Invalid dates are coerced to `NaN`.
return eq(+object, +other);
case errorTag:
return (
object.name == other.name && object.message == other.message
);
case regexpTag:
case stringTag:
// Coerce regexes to strings and treat strings, primitives and objects,
// as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
// for more details.
return object == other + "";
case mapTag:
var convert = mapToArray;
case setTag:
var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
convert || (convert = setToArray);
if (object.size != other.size && !isPartial) {
return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked) {
return stacked == other;
}
bitmask |= COMPARE_UNORDERED_FLAG;
// Recursively compare objects (susceptible to call stack limits).
stack.set(object, other);
var result = equalArrays(
convert(object),
convert(other),
bitmask,
customizer,
equalFunc,
stack
);
stack["delete"](object);
return result;
case symbolTag:
if (symbolValueOf) {
return (
symbolValueOf.call(object) == symbolValueOf.call(other)
);
}
}
return false;
}
/**
* A specialized version of `baseIsEqualDeep` for objects with support for
* partial deep comparisons.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function equalObjects(
object,
other,
bitmask,
customizer,
equalFunc,
stack
) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
objProps = getAllKeys(object),
objLength = objProps.length,
othProps = getAllKeys(other),
othLength = othProps.length;
if (objLength != othLength && !isPartial) {
return false;
}
var index = objLength;
while (index--) {
var key = objProps[index];
if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
return false;
}
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked && stack.get(other)) {
return stacked == other;
}
var result = true;
stack.set(object, other);
stack.set(other, object);
var skipCtor = isPartial;
while (++index < objLength) {
key = objProps[index];
var objValue = object[key],
othValue = other[key];
if (customizer) {
var compared = isPartial
? customizer(othValue, objValue, key, other, object, stack)
: customizer(objValue, othValue, key, object, other, stack);
}
// Recursively compare objects (susceptible to call stack limits).
if (
!(compared === undefined
? objValue === othValue ||
equalFunc(objValue, othValue, bitmask, customizer, stack)
: compared)
) {
result = false;
break;
}
skipCtor || (skipCtor = key == "constructor");
}
if (result && !skipCtor) {
var objCtor = object.constructor,
othCtor = other.constructor;
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
("constructor" in object && "constructor" in other) &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
typeof othCtor == "function" &&
othCtor instanceof othCtor
)
) {
result = false;
}
}
stack["delete"](object);
stack["delete"](other);
return result;
}
/**
* Creates an array of own enumerable property names and symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeys(object) {
return baseGetAllKeys(object, keys, getSymbols);
}
/**
* Creates an array of own and inherited enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeysIn(object) {
return baseGetAllKeys(object, keysIn, getSymbolsIn);
}
/**
* Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
* this function returns the custom method, otherwise it returns `baseIteratee`.
* If arguments are provided, the chosen function is invoked with them and
* its result is returned.
*
* @private
* @param {*} [value] The value to convert to an iteratee.
* @param {number} [arity] The arity of the created iteratee.
* @returns {Function} Returns the chosen function or its result.
*/
function getIteratee() {
var result = lodash.iteratee || iteratee;
result = result === iteratee ? baseIteratee : result;
return arguments.length ? result(arguments[0], arguments[1]) : result;
}
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/
function getMapData(map, key) {
var data = map.__data__;
return isKeyable(key)
? data[typeof key == "string" ? "string" : "hash"]
: data.map;
}
/**
* Gets the property names, values, and compare flags of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the match data of `object`.
*/
function getMatchData(object) {
var result = keys(object),
length = result.length;
while (length--) {
var key = result[length],
value = object[key];
result[length] = [key, value, isStrictComparable(value)];
}
return result;
}
/**
* Gets the native function at `key` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the method to get.
* @returns {*} Returns the function if it's native, else `undefined`.
*/
function getNative(object, key) {
var value = getValue(object, key);
return baseIsNative(value) ? value : undefined;
}
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag),
tag = value[symToStringTag];
try {
value[symToStringTag] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag;
} else {
delete value[symToStringTag];
}
}
return result;
}
/**
* Creates an array of the own enumerable symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/
var getSymbols = !nativeGetSymbols
? stubArray
: function(object) {
if (object == null) {
return [];
}
object = Object(object);
return arrayFilter(nativeGetSymbols(object), function(symbol) {
return propertyIsEnumerable.call(object, symbol);
});
};
/**
* Creates an array of the own and inherited enumerable symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/
var getSymbolsIn = !nativeGetSymbols
? stubArray
: function(object) {
var result = [];
while (object) {
arrayPush(result, getSymbols(object));
object = getPrototype(object);
}
return result;
};
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
var getTag = baseGetTag;
// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
if (
(DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
(Map && getTag(new Map()) != mapTag) ||
(Promise && getTag(Promise.resolve()) != promiseTag) ||
(Set && getTag(new Set()) != setTag) ||
(WeakMap && getTag(new WeakMap()) != weakMapTag)
) {
getTag = function(value) {
var result = baseGetTag(value),
Ctor = result == objectTag ? value.constructor : undefined,
ctorString = Ctor ? toSource(Ctor) : "";
if (ctorString) {
switch (ctorString) {
case dataViewCtorString:
return dataViewTag;
case mapCtorString:
return mapTag;
case promiseCtorString:
return promiseTag;
case setCtorString:
return setTag;
case weakMapCtorString:
return weakMapTag;
}
}
return result;
};
}
/**
* Checks if `path` exists on `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path to check.
* @param {Function} hasFunc The function to check properties.
* @returns {boolean} Returns `true` if `path` exists, else `false`.
*/
function hasPath(object, path, hasFunc) {
path = castPath(path, object);
var index = -1,
length = path.length,
result = false;
while (++index < length) {
var key = toKey(path[index]);
if (!(result = object != null && hasFunc(object, key))) {
break;
}
object = object[key];
}
if (result || ++index != length) {
return result;
}
length = object == null ? 0 : object.length;
return (
!!length &&
isLength(length) &&
isIndex(key, length) &&
(isArray(object) || isArguments(object))
);
}
/**
* Initializes an array clone.
*
* @private
* @param {Array} array The array to clone.
* @returns {Array} Returns the initialized clone.
*/
function initCloneArray(array) {
var length = array.length,
result = array.constructor(length);
// Add properties assigned by `RegExp#exec`.
if (
length &&
typeof array[0] == "string" &&
hasOwnProperty.call(array, "index")
) {
result.index = array.index;
result.input = array.input;
}
return result;
}
/**
* Initializes an object clone.
*
* @private
* @param {Object} object The object to clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneObject(object) {
return typeof object.constructor == "function" && !isPrototype(object)
? baseCreate(getPrototype(object))
: {};
}
/**
* Initializes an object clone based on its `toStringTag`.
*
* **Note:** This function only supports cloning values with tags of
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneByTag(object, tag, cloneFunc, isDeep) {
var Ctor = object.constructor;
switch (tag) {
case arrayBufferTag:
return cloneArrayBuffer(object);
case boolTag:
case dateTag:
return new Ctor(+object);
case dataViewTag:
return cloneDataView(object, isDeep);
case float32Tag:
case float64Tag:
case int8Tag:
case int16Tag:
case int32Tag:
case uint8Tag:
case uint8ClampedTag:
case uint16Tag:
case uint32Tag:
return cloneTypedArray(object, isDeep);
case mapTag:
return cloneMap(object, isDeep, cloneFunc);
case numberTag:
case stringTag:
return new Ctor(object);
case regexpTag:
return cloneRegExp(object);
case setTag:
return cloneSet(object, isDeep, cloneFunc);
case symbolTag:
return cloneSymbol(object);
}
}
/**
* Checks if `value` is a flattenable `arguments` object or array.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
*/
function isFlattenable(value) {
return (
isArray(value) ||
isArguments(value) ||
!!(spreadableSymbol && value && value[spreadableSymbol])
);
}
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex(value, length) {
length = length == null ? MAX_SAFE_INTEGER : length;
return (
!!length &&
(typeof value == "number" || reIsUint.test(value)) &&
(value > -1 && value % 1 == 0 && value < length)
);
}
/**
* Checks if the given arguments are from an iteratee call.
*
* @private
* @param {*} value The potential iteratee value argument.
* @param {*} index The potential iteratee index or key argument.
* @param {*} object The potential iteratee object argument.
* @returns {boolean} Returns `true` if the arguments are from an iteratee call,
* else `false`.
*/
function isIterateeCall(value, index, object) {
if (!isObject(object)) {
return false;
}
var type = typeof index;
if (
type == "number"
? isArrayLike(object) && isIndex(index, object.length)
: type == "string" && index in object
) {
return eq(object[index], value);
}
return false;
}
/**
* Checks if `value` is a property name and not a property path.
*
* @private
* @param {*} value The value to check.
* @param {Object} [object] The object to query keys on.
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
*/
function isKey(value, object) {
if (isArray(value)) {
return false;
}
var type = typeof value;
if (
type == "number" ||
type == "symbol" ||
type == "boolean" ||
value == null ||
isSymbol(value)
) {
return true;
}
return (
reIsPlainProp.test(value) ||
!reIsDeepProp.test(value) ||
(object != null && value in Object(object))
);
}
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/
function isKeyable(value) {
var type = typeof value;
return type == "string" ||
type == "number" ||
type == "symbol" ||
type == "boolean"
? value !== "__proto__"
: value === null;
}
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/
function isMasked(func) {
return !!maskSrcKey && maskSrcKey in func;
}
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype(value) {
var Ctor = value && value.constructor,
proto =
(typeof Ctor == "function" && Ctor.prototype) || objectProto;
return value === proto;
}
/**
* Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` if suitable for strict
* equality comparisons, else `false`.
*/
function isStrictComparable(value) {
return value === value && !isObject(value);
}
/**
* A specialized version of `matchesProperty` for source values suitable
* for strict equality comparisons, i.e. `===`.
*
* @private
* @param {string} key The key of the property to get.
* @param {*} srcValue The value to match.
* @returns {Function} Returns the new spec function.
*/
function matchesStrictComparable(key, srcValue) {
return function(object) {
if (object == null) {
return false;
}
return (
object[key] === srcValue &&
(srcValue !== undefined || key in Object(object))
);
};
}
/**
* A specialized version of `_.memoize` which clears the memoized function's
* cache when it exceeds `MAX_MEMOIZE_SIZE`.
*
* @private
* @param {Function} func The function to have its output memoized.
* @returns {Function} Returns the new memoized function.
*/
function memoizeCapped(func) {
var result = memoize(func, function(key) {
if (cache.size === MAX_MEMOIZE_SIZE) {
cache.clear();
}
return key;
});
var cache = result.cache;
return result;
}
/**
* This function is like
* [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* except that it includes inherited enumerable properties.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function nativeKeysIn(object) {
var result = [];
if (object != null) {
for (var key in Object(object)) {
result.push(key);
}
}
return result;
}
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}
/**
* A specialized version of `baseRest` which transforms the rest array.
*
* @private
* @param {Function} func The function to apply a rest parameter to.
* @param {number} [start=func.length-1] The start position of the rest parameter.
* @param {Function} transform The rest array transform.
* @returns {Function} Returns the new function.
*/
function overRest(func, start, transform) {
start = nativeMax(start === undefined ? func.length - 1 : start, 0);
return function() {
var args = arguments,
index = -1,
length = nativeMax(args.length - start, 0),
array = Array(length);
while (++index < length) {
array[index] = args[start + index];
}
index = -1;
var otherArgs = Array(start + 1);
while (++index < start) {
otherArgs[index] = args[index];
}
otherArgs[start] = transform(array);
return apply(func, this, otherArgs);
};
}
/**
* Sets the `toString` method of `func` to return `string`.
*
* @private
* @param {Function} func The function to modify.
* @param {Function} string The `toString` result.
* @returns {Function} Returns `func`.
*/
var setToString = shortOut(baseSetToString);
/**
* Creates a function that'll short out and invoke `identity` instead
* of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
* milliseconds.
*
* @private
* @param {Function} func The function to restrict.
* @returns {Function} Returns the new shortable function.
*/
function shortOut(func) {
var count = 0,
lastCalled = 0;
return function() {
var stamp = nativeNow(),
remaining = HOT_SPAN - (stamp - lastCalled);
lastCalled = stamp;
if (remaining > 0) {
if (++count >= HOT_COUNT) {
return arguments[0];
}
} else {
count = 0;
}
return func.apply(undefined, arguments);
};
}
/**
* Converts `string` to a property path array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the property path array.
*/
var stringToPath = memoizeCapped(function(string) {
var result = [];
if (reLeadingDot.test(string)) {
result.push("");
}
string.replace(rePropName, function(match, number, quote, string) {
result.push(
quote ? string.replace(reEscapeChar, "$1") : number || match
);
});
return result;
});
/**
* Converts `value` to a string key if it's not a string or symbol.
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value) {
if (typeof value == "string" || isSymbol(value)) {
return value;
}
var result = value + "";
return result == "0" && 1 / value == -INFINITY ? "-0" : result;
}
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to convert.
* @returns {string} Returns the source code.
*/
function toSource(func) {
if (func != null) {
try {
return funcToString.call(func);
} catch (e) {}
try {
return func + "";
} catch (e) {}
}
return "";
}
/*------------------------------------------------------------------------*/
/**
* Creates an array of unique values, in order, from all given arrays using
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Array
* @param {...Array} [arrays] The arrays to inspect.
* @returns {Array} Returns the new array of combined values.
* @example
*
* _.union([2], [1, 2]);
* // => [2, 1]
*/
var union = baseRest(function(arrays) {
return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
});
/*------------------------------------------------------------------------*/
/**
* Iterates over elements of `collection` and invokes `iteratee` for each element.
* The iteratee is invoked with three arguments: (value, index|key, collection).
* Iteratee functions may exit iteration early by explicitly returning `false`.
*
* **Note:** As with other "Collections" methods, objects with a "length"
* property are iterated like arrays. To avoid this behavior use `_.forIn`
* or `_.forOwn` for object iteration.
*
* @static
* @memberOf _
* @since 0.1.0
* @alias each
* @category Collection
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
* @returns {Array|Object} Returns `collection`.
* @see _.forEachRight
* @example
*
* _.forEach([1, 2], function(value) {
* console.log(value);
* });
* // => Logs `1` then `2`.
*
* _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
* console.log(key);
* });
* // => Logs 'a' then 'b' (iteration order is not guaranteed).
*/
function forEach(collection, iteratee) {
var func = isArray(collection) ? arrayEach : baseEach;
return func(collection, getIteratee(iteratee, 3));
}
/**
* Checks if `value` is in `collection`. If `collection` is a string, it's
* checked for a substring of `value`, otherwise
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* is used for equality comparisons. If `fromIndex` is negative, it's used as
* the offset from the end of `collection`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Collection
* @param {Array|Object|string} collection The collection to inspect.
* @param {*} value The value to search for.
* @param {number} [fromIndex=0] The index to search from.
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
* @returns {boolean} Returns `true` if `value` is found, else `false`.
* @example
*
* _.includes([1, 2, 3], 1);
* // => true
*
* _.includes([1, 2, 3], 1, 2);
* // => false
*
* _.includes({ 'a': 1, 'b': 2 }, 1);
* // => true
*
* _.includes('abcd', 'bc');
* // => true
*/
function includes(collection, value, fromIndex, guard) {
collection = isArrayLike(collection) ? collection : values(collection);
fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
var length = collection.length;
if (fromIndex < 0) {
fromIndex = nativeMax(length + fromIndex, 0);
}
return isString(collection)
? fromIndex <= length && collection.indexOf(value, fromIndex) > -1
: !!length && baseIndexOf(collection, value, fromIndex) > -1;
}
/*------------------------------------------------------------------------*/
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `_.memoize.Cache`
* constructor with one whose instances implement the
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
* method interface of `clear`, `delete`, `get`, `has`, and `set`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* var object = { 'a': 1, 'b': 2 };
* var other = { 'c': 3, 'd': 4 };
*
* var values = _.memoize(_.values);
* values(object);
* // => [1, 2]
*
* values(other);
* // => [3, 4]
*
* object.a = 2;
* values(object);
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b']);
* values(object);
* // => ['a', 'b']
*
* // Replace `_.memoize.Cache`.
* _.memoize.Cache = WeakMap;
*/
function memoize(func, resolver) {
if (
typeof func != "function" ||
(resolver != null && typeof resolver != "function")
) {
throw new TypeError(FUNC_ERROR_TEXT);
}
var memoized = function() {
var args = arguments,
key = resolver ? resolver.apply(this, args) : args[0],
cache = memoized.cache;
if (cache.has(key)) {
return cache.get(key);
}
var result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new (memoize.Cache || MapCache)();
return memoized;
}
// Expose `MapCache`.
memoize.Cache = MapCache;
/*------------------------------------------------------------------------*/
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/
function eq(value, other) {
return value === other || (value !== value && other !== other);
}
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
var isArguments = baseIsArguments(
(function() {
return arguments;
})()
)
? baseIsArguments
: function(value) {
return (
isObjectLike(value) &&
hasOwnProperty.call(value, "callee") &&
!propertyIsEnumerable.call(value, "callee")
);
};
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray = Array.isArray;
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
/**
* This method is like `_.isArrayLike` except that it also checks if `value`
* is an object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* _.isArrayLikeObject([1, 2, 3]);
* // => true
*
* _.isArrayLikeObject(document.body.children);
* // => true
*
* _.isArrayLikeObject('abc');
* // => false
*
* _.isArrayLikeObject(_.noop);
* // => false
*/
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value);
}
/**
* Checks if `value` is a buffer.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
* @example
*
* _.isBuffer(new Buffer(2));
* // => true
*
* _.isBuffer(new Uint8Array(2));
* // => false
*/
var isBuffer = nativeIsBuffer || stubFalse;
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction(value) {
if (!isObject(value)) {
return false;
}
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 9 which returns 'object' for typed arrays and other constructors.
var tag = baseGetTag(value);
return (
tag == funcTag ||
tag == genTag ||
tag == asyncTag ||
tag == proxyTag
);
}
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/
function isLength(value) {
return (
typeof value == "number" &&
value > -1 &&
value % 1 == 0 &&
value <= MAX_SAFE_INTEGER
);
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return value != null && (type == "object" || type == "function");
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == "object";
}
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
return false;
}
var proto = getPrototype(value);
if (proto === null) {
return true;
}
var Ctor =
hasOwnProperty.call(proto, "constructor") && proto.constructor;
return (
typeof Ctor == "function" &&
Ctor instanceof Ctor &&
funcToString.call(Ctor) == objectCtorString
);
}
/**
* Checks if `value` is classified as a `String` primitive or object.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a string, else `false`.
* @example
*
* _.isString('abc');
* // => true
*
* _.isString(1);
* // => false
*/
function isString(value) {
return (
typeof value == "string" ||
(!isArray(value) &&
isObjectLike(value) &&
baseGetTag(value) == stringTag)
);
}
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return (
typeof value == "symbol" ||
(isObjectLike(value) && baseGetTag(value) == symbolTag)
);
}
/**
* Checks if `value` is classified as a typed array.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
* @example
*
* _.isTypedArray(new Uint8Array);
* // => true
*
* _.isTypedArray([]);
* // => false
*/
var isTypedArray = nodeIsTypedArray
? baseUnary(nodeIsTypedArray)
: baseIsTypedArray;
/**
* Checks if `value` is `undefined`.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
* @example
*
* _.isUndefined(void 0);
* // => true
*
* _.isUndefined(null);
* // => false
*/
function isUndefined(value) {
return value === undefined;
}
/**
* Converts `value` to an array.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Lang
* @param {*} value The value to convert.
* @returns {Array} Returns the converted array.
* @example
*
* _.toArray({ 'a': 1, 'b': 2 });
* // => [1, 2]
*
* _.toArray('abc');
* // => ['a', 'b', 'c']
*
* _.toArray(1);
* // => []
*
* _.toArray(null);
* // => []
*/
function toArray(value) {
if (!value) {
return [];
}
if (isArrayLike(value)) {
return isString(value) ? stringToArray(value) : copyArray(value);
}
if (symIterator && value[symIterator]) {
return iteratorToArray(value[symIterator]());
}
var tag = getTag(value),
func =
tag == mapTag
? mapToArray
: tag == setTag
? setToArray
: values;
return func(value);
}
/**
* Converts `value` to a finite number.
*
* @static
* @memberOf _
* @since 4.12.0
* @category Lang
* @param {*} value The value to convert.
* @returns {number} Returns the converted number.
* @example
*
* _.toFinite(3.2);
* // => 3.2
*
* _.toFinite(Number.MIN_VALUE);
* // => 5e-324
*
* _.toFinite(Infinity);
* // => 1.7976931348623157e+308
*
* _.toFinite('3.2');
* // => 3.2
*/
function toFinite(value) {
if (!value) {
return value === 0 ? value : 0;
}
value = toNumber(value);
if (value === INFINITY || value === -INFINITY) {
var sign = value < 0 ? -1 : 1;
return sign * MAX_INTEGER;
}
return value === value ? value : 0;
}
/**
* Converts `value` to an integer.
*
* **Note:** This method is loosely based on
* [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {number} Returns the converted integer.
* @example
*
* _.toInteger(3.2);
* // => 3
*
* _.toInteger(Number.MIN_VALUE);
* // => 0
*
* _.toInteger(Infinity);
* // => 1.7976931348623157e+308
*
* _.toInteger('3.2');
* // => 3
*/
function toInteger(value) {
var result = toFinite(value),
remainder = result % 1;
return result === result
? remainder
? result - remainder
: result
: 0;
}
/**
* Converts `value` to a number.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {number} Returns the number.
* @example
*
* _.toNumber(3.2);
* // => 3.2
*
* _.toNumber(Number.MIN_VALUE);
* // => 5e-324
*
* _.toNumber(Infinity);
* // => Infinity
*
* _.toNumber('3.2');
* // => 3.2
*/
function toNumber(value) {
if (typeof value == "number") {
return value;
}
if (isSymbol(value)) {
return NAN;
}
if (isObject(value)) {
var other =
typeof value.valueOf == "function" ? value.valueOf() : value;
value = isObject(other) ? other + "" : other;
}
if (typeof value != "string") {
return value === 0 ? value : +value;
}
value = value.replace(reTrim, "");
var isBinary = reIsBinary.test(value);
return isBinary || reIsOctal.test(value)
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: reIsBadHex.test(value)
? NAN
: +value;
}
/**
* Converts `value` to a plain object flattening inherited enumerable string
* keyed properties of `value` to own properties of the plain object.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {Object} Returns the converted plain object.
* @example
*
* function Foo() {
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.assign({ 'a': 1 }, new Foo);
* // => { 'a': 1, 'b': 2 }
*
* _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
* // => { 'a': 1, 'b': 2, 'c': 3 }
*/
function toPlainObject(value) {
return copyObject(value, keysIn(value));
}
/**
* Converts `value` to a string. An empty string is returned for `null`
* and `undefined` values. The sign of `-0` is preserved.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
* @example
*
* _.toString(null);
* // => ''
*
* _.toString(-0);
* // => '-0'
*
* _.toString([1, 2, 3]);
* // => '1,2,3'
*/
function toString(value) {
return value == null ? "" : baseToString(value);
}
/*------------------------------------------------------------------------*/
/**
* Gets the value at `path` of `object`. If the resolved value is
* `undefined`, the `defaultValue` is returned in its place.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
* @returns {*} Returns the resolved value.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.get(object, 'a[0].b.c');
* // => 3
*
* _.get(object, ['a', '0', 'b', 'c']);
* // => 3
*
* _.get(object, 'a.b.c', 'default');
* // => 'default'
*/
function get(object, path, defaultValue) {
var result = object == null ? undefined : baseGet(object, path);
return result === undefined ? defaultValue : result;
}
/**
* Checks if `path` is a direct or inherited property of `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Object
* @param {Object} object The object to query.
* @param {Array|string} path The path to check.
* @returns {boolean} Returns `true` if `path` exists, else `false`.
* @example
*
* var object = _.create({ 'a': _.create({ 'b': 2 }) });
*
* _.hasIn(object, 'a');
* // => true
*
* _.hasIn(object, 'a.b');
* // => true
*
* _.hasIn(object, ['a', 'b']);
* // => true
*
* _.hasIn(object, 'b');
* // => false
*/
function hasIn(object, path) {
return object != null && hasPath(object, path, baseHasIn);
}
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/
function keys(object) {
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}
/**
* Creates an array of the own and inherited enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keysIn(new Foo);
* // => ['a', 'b', 'c'] (iteration order is not guaranteed)
*/
function keysIn(object) {
return isArrayLike(object)
? arrayLikeKeys(object, true)
: baseKeysIn(object);
}
/**
* This method is like `_.assign` except that it recursively merges own and
* inherited enumerable string keyed properties of source objects into the
* destination object. Source properties that resolve to `undefined` are
* skipped if a destination value exists. Array and plain object properties
* are merged recursively. Other objects and value types are overridden by
* assignment. Source objects are applied from left to right. Subsequent
* sources overwrite property assignments of previous sources.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 0.5.0
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @returns {Object} Returns `object`.
* @example
*
* var object = {
* 'a': [{ 'b': 2 }, { 'd': 4 }]
* };
*
* var other = {
* 'a': [{ 'c': 3 }, { 'e': 5 }]
* };
*
* _.merge(object, other);
* // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
*/
var merge = createAssigner(function(object, source, srcIndex) {
baseMerge(object, source, srcIndex);
});
/**
* Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
* it's created. Arrays are created for missing index properties while objects
* are created for all other missing properties. Use `_.setWith` to customize
* `path` creation.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @returns {Object} Returns `object`.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.set(object, 'a[0].b.c', 4);
* console.log(object.a[0].b.c);
* // => 4
*
* _.set(object, ['x', '0', 'y', 'z'], 5);
* console.log(object.x[0].y.z);
* // => 5
*/
function set(object, path, value) {
return object == null ? object : baseSet(object, path, value);
}
/**
* Creates an array of the own enumerable string keyed property values of `object`.
*
* **Note:** Non-object values are coerced to objects.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property values.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.values(new Foo);
* // => [1, 2] (iteration order is not guaranteed)
*
* _.values('hi');
* // => ['h', 'i']
*/
function values(object) {
return object == null ? [] : baseValues(object, keys(object));
}
/*------------------------------------------------------------------------*/
/**
* Creates a function that returns `value`.
*
* @static
* @memberOf _
* @since 2.4.0
* @category Util
* @param {*} value The value to return from the new function.
* @returns {Function} Returns the new constant function.
* @example
*
* var objects = _.times(2, _.constant({ 'a': 1 }));
*
* console.log(objects);
* // => [{ 'a': 1 }, { 'a': 1 }]
*
* console.log(objects[0] === objects[1]);
* // => true
*/
function constant(value) {
return function() {
return value;
};
}
/**
* This method returns the first argument it receives.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Util
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var object = { 'a': 1 };
*
* console.log(_.identity(object) === object);
* // => true
*/
function identity(value) {
return value;
}
/**
* Creates a function that invokes `func` with the arguments of the created
* function. If `func` is a property name, the created function returns the
* property value for a given element. If `func` is an array or object, the
* created function returns `true` for elements that contain the equivalent
* source properties, otherwise it returns `false`.
*
* @static
* @since 4.0.0
* @memberOf _
* @category Util
* @param {*} [func=_.identity] The value to convert to a callback.
* @returns {Function} Returns the callback.
* @example
*
* var users = [
* { 'user': 'barney', 'age': 36, 'active': true },
* { 'user': 'fred', 'age': 40, 'active': false }
* ];
*
* // The `_.matches` iteratee shorthand.
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
*
* // The `_.matchesProperty` iteratee shorthand.
* _.filter(users, _.iteratee(['user', 'fred']));
* // => [{ 'user': 'fred', 'age': 40 }]
*
* // The `_.property` iteratee shorthand.
* _.map(users, _.iteratee('user'));
* // => ['barney', 'fred']
*
* // Create custom iteratee shorthands.
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
* return func.test(string);
* };
* });
*
* _.filter(['abc', 'def'], /ef/);
* // => ['def']
*/
function iteratee(func) {
return baseIteratee(
typeof func == "function" ? func : baseClone(func, CLONE_DEEP_FLAG)
);
}
/**
* This method returns `undefined`.
*
* @static
* @memberOf _
* @since 2.3.0
* @category Util
* @example
*
* _.times(2, _.noop);
* // => [undefined, undefined]
*/
function noop() {
// No operation performed.
}
/**
* Creates a function that returns the value at `path` of a given object.
*
* @static
* @memberOf _
* @since 2.4.0
* @category Util
* @param {Array|string} path The path of the property to get.
* @returns {Function} Returns the new accessor function.
* @example
*
* var objects = [
* { 'a': { 'b': 2 } },
* { 'a': { 'b': 1 } }
* ];
*
* _.map(objects, _.property('a.b'));
* // => [2, 1]
*
* _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
* // => [1, 2]
*/
function property(path) {
return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
}
/**
* This method returns a new empty array.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {Array} Returns the new empty array.
* @example
*
* var arrays = _.times(2, _.stubArray);
*
* console.log(arrays);
* // => [[], []]
*
* console.log(arrays[0] === arrays[1]);
* // => false
*/
function stubArray() {
return [];
}
/**
* This method returns `false`.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {boolean} Returns `false`.
* @example
*
* _.times(2, _.stubFalse);
* // => [false, false]
*/
function stubFalse() {
return false;
}
/*------------------------------------------------------------------------*/
// Add methods that return wrapped values in chain sequences.
lodash.constant = constant;
lodash.iteratee = iteratee;
lodash.keys = keys;
lodash.keysIn = keysIn;
lodash.memoize = memoize;
lodash.merge = merge;
lodash.property = property;
lodash.set = set;
lodash.toArray = toArray;
lodash.toPlainObject = toPlainObject;
lodash.union = union;
lodash.values = values;
/*------------------------------------------------------------------------*/
// Add methods that return unwrapped values in chain sequences.
lodash.eq = eq;
lodash.forEach = forEach;
lodash.get = get;
lodash.hasIn = hasIn;
lodash.identity = identity;
lodash.includes = includes;
lodash.isArguments = isArguments;
lodash.isArray = isArray;
lodash.isArrayLike = isArrayLike;
lodash.isArrayLikeObject = isArrayLikeObject;
lodash.isBuffer = isBuffer;
lodash.isFunction = isFunction;
lodash.isLength = isLength;
lodash.isObject = isObject;
lodash.isObjectLike = isObjectLike;
lodash.isPlainObject = isPlainObject;
lodash.isString = isString;
lodash.isSymbol = isSymbol;
lodash.isTypedArray = isTypedArray;
lodash.isUndefined = isUndefined;
lodash.stubArray = stubArray;
lodash.stubFalse = stubFalse;
lodash.noop = noop;
lodash.toFinite = toFinite;
lodash.toInteger = toInteger;
lodash.toNumber = toNumber;
lodash.toString = toString;
// Add aliases.
lodash.each = forEach;
/*------------------------------------------------------------------------*/
/**
* The semantic version number.
*
* @static
* @memberOf _
* @type {string}
*/
lodash.VERSION = VERSION;
/*--------------------------------------------------------------------------*/
if (freeModule) {
// Export for Node.js.
(freeModule.exports = lodash)._ = lodash;
// Export for CommonJS support.
freeExports._ = lodash;
}
}.call(this));
================================================
FILE: packages/browser-sync/lib/logger.js
================================================
"use strict";
var messages = require("./connect-utils");
var utils = require("./utils");
var _ = require("./lodash.custom");
var chalk = require("chalk");
var template = (prefix) => "[" + chalk.blue(prefix) + "] "
var logger = require("eazy-logger").Logger({
useLevelPrefixes: false
});
module.exports.logger = logger;
/**
* @param name
* @returns {*}
*/
module.exports.getLogger = function(name) {
return logger.clone(function(config) {
config.prefix = config.prefix + template(name)
return config;
});
};
/**
* Logging Callbacks
*/
module.exports.callbacks = {
/**
* Log when file-watching has started
* @param {BrowserSync} bs
* @param data
*/
"file:watching": function(bs, data) {
if (Object.keys(data).length) {
logger.info("Watching files...");
}
},
/**
* Log when a file changes
* @param {BrowserSync} bs
* @param data
*/
"file:reload": function(bs, data) {
if (canLogFileChange(bs, data)) {
if (data.path[0] === "*") {
return logger.info(
chalk.cyan("Reloading files that match: %s"),
chalk.magenta(data.path)
);
}
logger.info(
chalk.cyan("File event [%s] : %s"),
data.event,
chalk.magenta(data.path),
);
}
},
/**
*
*/
"service:exit": function() {
logger.debug("Exiting...");
},
/**
*
*/
"browser:reload": function(bs, data = {}) {
if (canLogFileChange(bs)) {
if (data.files && data.files.length > 1) {
return logger.info(
chalk.cyan(`Reloading Browsers... (buffered %s events)`),
data.files.length
);
}
logger.info(chalk.cyan("Reloading Browsers..."));
}
},
/**
*
*/
"browser:error": function() {
logger.error(
"Couldn't open browser (if you are using BrowserSync in a " +
"headless environment, you might want to set the %s option to %s)",
chalk.cyan("open"),
chalk.cyan("false"),
);
},
/**
* @param {BrowserSync} bs
* @param data
*/
"stream:changed": function(bs, data) {
if (canLogFileChange(bs)) {
var changed = data.changed;
logger.info(
chalk.cyan("%s %s changed (%s)"),
changed.length,
changed.length > 1 ? "files" : "file",
chalk.magenta(changed.join(", "))
);
}
},
/**
* Client connected logging
* @param {BrowserSync} bs
* @param data
*/
"client:connected": function(bs, data) {
var uaString = utils.getUaString(data.ua);
var msg = chalk.cyan("Browser Connected: %s, version: %s");
var method = "info";
if (!bs.options.get("logConnections")) {
method = "debug";
}
logger.log(method, msg, chalk.magenta(uaString.name), chalk.magenta(uaString.version));
},
/**
* Main logging when the service is running
* @param {BrowserSync} bs
* @param data
*/
"service:running": function(bs, data) {
const type = data.type;
if (bs.options.get("json")) {
return console.log(
JSON.stringify({
"service:running": {
options: bs.options.toJS()
}
})
);
}
if (type === "server") {
var baseDir = bs.options.getIn(["server", "baseDir"]);
logUrls(bs.options.get("urls").toJS());
if (baseDir) {
if (utils.isList(baseDir)) {
baseDir.forEach(serveFiles);
} else {
serveFiles(baseDir);
}
}
}
if (type === "proxy") {
logger.info(
"Proxying: %s",
chalk.cyan(bs.options.getIn(["proxy", "target"]))
);
logUrls(bs.options.get("urls").toJS());
}
if (type === "snippet") {
if (bs.options.get("logSnippet")) {
logger.info(
chalk.bold(`Copy the following snippet into your website, just before the closing ${chalk.cyan('')} tag`)
);
logger.unprefixed("info", messages.scriptTags(bs.options));
}
logUrls(
bs.options
.get("urls")
.filter(function(value, key) {
return key.slice(0, 2) === "ui";
})
.toJS()
);
}
function serveFiles(base) {
logger.info("Serving files from: %s", chalk.magenta(base));
}
}
};
/**
* Plugin interface for BrowserSync
* @param {EventEmitter} emitter
* @param {BrowserSync} bs
* @returns {Object}
*/
module.exports.plugin = function(emitter, bs) {
var logPrefix = bs.options.get("logPrefix");
var logLevel = bs.options.get("logLevel");
// Should set logger level here!
logger.setLevel(logLevel);
if (logPrefix) {
if (_.isFunction(logPrefix)) {
logger.setPrefix(logPrefix);
} else {
logger.setPrefix(template(logPrefix));
}
}
_.each(exports.callbacks, function(func, event) {
emitter.on(event, func.bind(this, bs));
});
return logger;
};
/**
*
* @param urls
*/
function logUrls(urls) {
var keys = Object.keys(urls);
var longestName = 0;
var longesturl = 0;
var offset = 2;
if (!keys.length) {
return;
}
var names = keys.map(function(key) {
if (key.length > longestName) {
longestName = key.length;
}
if (urls[key].length > longesturl) {
longesturl = urls[key].length;
}
return key;
});
var underline = getChars(longestName + offset + longesturl + 1, "-");
var underlined = false;
logger.info(chalk.bold("Access URLs:"));
logger.unprefixed("info", " %s", chalk.grey(underline));
keys.forEach(function(key, i) {
var keyname = getKeyName(key);
logger.unprefixed(
"info",
" %s: %s",
getPadding(key.length, longestName + offset) + keyname,
chalk.magenta(urls[key])
);
if (!underlined && names[i + 1] && names[i + 1].indexOf("ui") > -1) {
underlined = true;
logger.unprefixed("info", " %s", chalk.grey(underline));
}
});
logger.unprefixed("info", " %s", chalk.grey(underline));
}
/**
* @param {Number} len
* @param {Number} max
* @returns {string}
*/
function getPadding(len, max) {
return new Array(max - (len + 1)).join(" ");
}
/**
* @param {Number} len
* @param {String} char
* @returns {string}
*/
function getChars(len, char) {
return new Array(len).join(char);
}
/**
* Transform url-key names into something more presentable
* @param key
* @returns {string}
*/
function getKeyName(key) {
if (key.indexOf("ui") > -1) {
if (key === "ui") {
return "UI";
}
if (key === "ui-external") {
return "UI External";
}
}
return key.substr(0, 1).toUpperCase() + key.substring(1);
}
/**
* Determine if file changes should be logged
* @param bs
* @param data
* @returns {boolean}
*/
function canLogFileChange(bs, data) {
if (data && data.log === false) {
return false;
}
return bs.options.get("logFileChanges");
}
================================================
FILE: packages/browser-sync/lib/options.ts
================================================
import { BsTempOptions, TransformResult } from "./cli/cli-options";
const _ = require("./lodash.custom");
import * as Immutable from "immutable";
import * as defaultConfig from "./default-config";
/**
* Move top-level ws options to proxy.ws
* This is to allow it to be set from the CLI
* @param incoming
*/
export function setProxyWs(incoming: BsTempOptions): TransformResult {
if (incoming.get("ws") && incoming.get("mode") === "proxy") {
return [incoming.setIn(["proxy", "ws"], true), []];
}
return [incoming, []];
}
/**
* @param incoming
*/
export function setOpen(incoming: BsTempOptions): TransformResult {
return [
incoming.update("open", function(open) {
if (incoming.get("mode") === "snippet") {
if (open !== "ui" && open !== "ui-external") {
return false;
}
}
return open;
}),
[]
];
}
/**
* Set the running mode
* @param incoming
*/
export function setMode(incoming: BsTempOptions): TransformResult {
const output = incoming.set(
"mode",
(function() {
if (incoming.get("server")) {
return "server";
}
if (incoming.get("proxy")) {
return "proxy";
}
return "snippet";
})()
);
return [output, []];
}
/**
* @param incoming
*/
export function setScheme(incoming: BsTempOptions): TransformResult {
var scheme = "http";
if (incoming.getIn(["server", "https"])) {
scheme = "https";
}
if (incoming.get("https")) {
scheme = "https";
}
if (incoming.getIn(["proxy", "url", "protocol"])) {
if (incoming.getIn(["proxy", "url", "protocol"]) === "https:") {
scheme = "https";
}
}
return [incoming.set("scheme", scheme), []];
}
/**
* @param incoming
*/
export function setStartPath(incoming: BsTempOptions): TransformResult {
if (incoming.get("proxy")) {
var path = incoming.getIn(["proxy", "url", "path"]);
if (path !== "/") {
return [incoming.set("startPath", path), []];
}
}
return [incoming, []];
}
/**
* @param incoming
*/
export function setNamespace(incoming: BsTempOptions): TransformResult {
var namespace = incoming.getIn(["socket", "namespace"]);
if (_.isFunction(namespace)) {
return [
incoming.setIn(
["socket", "namespace"],
namespace((defaultConfig.socket as any).namespace)
),
[]
];
}
return [incoming, []];
}
/**
* @param incoming
*/
export function setServerOpts(incoming: BsTempOptions): TransformResult {
if (!incoming.get("server")) {
return [incoming, []];
}
var indexarg = incoming.getIn(["server", "index"]) || "index.html";
var optPath = ["server", "serveStaticOptions"];
if (!incoming.getIn(optPath)) {
return [
incoming.setIn(
optPath,
Immutable.Map({
index: indexarg
})
),
[]
];
}
if (!incoming.hasIn(optPath.concat(["index"]))) {
return [incoming.setIn(optPath.concat(["index"]), indexarg), []];
}
return [incoming, []];
}
export function liftExtensionsOptionFromCli(
incoming: BsTempOptions
): TransformResult {
// cli extensions
var optPath = ["server", "serveStaticOptions"];
if (incoming.get("extensions")) {
return [
incoming.setIn(
optPath.concat(["extensions"]),
incoming.get("extensions")
),
[]
];
}
return [incoming, []];
}
/**
* Back-compat fixes for rewriteRules being set to a boolean
*/
export function fixRewriteRules(incoming: BsTempOptions): TransformResult {
return [
incoming.update("rewriteRules", function(rr) {
return Immutable.List([])
.concat(rr)
.filter(Boolean);
}),
[]
];
}
export function fixSnippetIgnorePaths(
incoming: BsTempOptions
): TransformResult {
var ignorePaths = incoming.getIn(["snippetOptions", "ignorePaths"]);
if (ignorePaths) {
if (_.isString(ignorePaths)) {
ignorePaths = [ignorePaths];
}
ignorePaths = ignorePaths.map(ensureSlash);
return [
incoming.setIn(
["snippetOptions", "blacklist"],
Immutable.List(ignorePaths)
),
[]
];
}
return [incoming, []];
}
export function fixSnippetIncludePaths(
incoming: BsTempOptions
): TransformResult {
var includePaths = incoming.getIn(["snippetOptions", "whitelist"]);
if (includePaths) {
includePaths = includePaths.map(ensureSlash);
return [
incoming.setIn(
["snippetOptions", "whitelist"],
Immutable.List(includePaths)
),
[]
];
}
return [incoming, []];
}
/**
* Enforce paths to begin with a forward slash
*/
function ensureSlash(item) {
if (item[0] !== "/") {
return "/" + item;
}
return item;
}
/**
*
*/
export function setMiddleware(incoming: BsTempOptions): TransformResult {
var mw = getMiddlwares(incoming);
return [incoming.set("middleware", mw), []];
}
/**
* top-level option, or given as part of the proxy/server option
* @param item
* @returns {*}
*/
function getMiddlwares(item) {
var mw = item.get("middleware");
var serverMw = item.getIn(["server", "middleware"]);
var proxyMw = item.getIn(["proxy", "middleware"]);
var list = Immutable.List([]);
if (mw) {
return listMerge(list, mw);
}
if (serverMw) {
return listMerge(list, serverMw);
}
if (proxyMw) {
return listMerge(list, proxyMw);
}
return list;
}
/**
* @param item
* @returns {*}
*/
function isList(item) {
return Immutable.List.isList(item);
}
/**
* @param list
* @param item
* @returns {*}
*/
function listMerge(list, item) {
if (_.isFunction(item)) {
list = list.push(item);
}
if (isList(item) && item.size) {
list = list.merge(item);
}
return list;
}
/**
* @param incoming
* @returns {*}
*/
export function setUiPort(incoming: BsTempOptions): TransformResult {
if (incoming.get("uiPort")) {
return [incoming.setIn(["ui", "port"], incoming.get("uiPort")), []];
}
return [incoming, []];
}
================================================
FILE: packages/browser-sync/lib/plugins.js
================================================
var Immutable = require("immutable");
var Map = Immutable.Map;
var isMap = Immutable.Map.isMap;
var List = Immutable.List;
var path = require("path");
var fs = require("fs");
const { parseParams } = require("./utils");
var Plugin = Immutable.Record({
moduleName: "",
name: "",
active: true,
module: undefined,
options: Map({}),
via: "inline",
dir: process.cwd(),
init: undefined,
errors: List([])
});
/**
* Accept a string/object
* and resolve it into the plugin format above
* @param item
* @returns {*}
*/
function resolvePlugin(item) {
/**
* Handle when string was given, such as plugins: ['bs-html-injector']
*/
if (typeof item === "string") {
return getFromString(item);
}
if (!isMap(item)) {
return new Plugin().mergeDeep({
errors: [new Error("Plugin not supported in this format")]
});
}
if (item.has("module")) {
var nameOrObj = item.get("module");
var options = item.get("options");
/**
* The 'module' key can be a string, this allows
* inline plugin references, but with options
* eg:
*
* bs.init({
* plugins: [
* {
* module: './myjs-file.js'
* options: {
* files: "*.html"
* }
* }
* ]
* });
*/
if (typeof nameOrObj === "string") {
return getFromString(nameOrObj).mergeDeep({
options: options
});
}
/**
* If the plugin was given completely inline (because it needs options)
* eg:
*
* bs.init({
* plugins: [
* {
* module: {
* plugin: function() {
* console.log('My plugin code')
* }
* },
* options: {
* files: "*.html"
* }
* }
* ]
* })
*/
if (Immutable.Map.isMap(nameOrObj)) {
return new Plugin({
module: nameOrObj,
options: options
});
}
}
/**
* If a module was given directly. For example, ater calling require.
*
* eg:
* var myplugin = require('./some-js');
* bs.init({plugins: [myplugin]});
*/
if (item.has("plugin")) {
return new Plugin({
module: item
});
}
/**
* If we reach here, the plugin option was used incorrectly
*/
return new Plugin().mergeDeep({
errors: [new Error("Plugin was not configured correctly")]
});
}
module.exports.resolvePlugin = resolvePlugin;
/**
* Load a plugin from disk
* @param item
* @returns {*}
*/
function requirePlugin(item) {
/**
* if the "module" property already exists and
* is not a string, then we bail and don't bother looking
* for the file
*/
if (item.get("module") && typeof item.get("module") !== "string") {
return item;
}
try {
/**
* Try a raw node require() call - this will be how
* regular "npm installed" plugins wil work
*/
var maybe = require.resolve(item.get("name"));
return item.set("module", require(maybe));
} catch (e) {
/**
* If require threw an MODULE_NOT_FOUND error, try again
* by resolving from cwd. This is needed since cli
* users will not add ./ to the front of a path (which
* node requires to resolve from cwd)
*/
if (e.code === "MODULE_NOT_FOUND") {
var maybe = path.resolve(process.cwd(), item.get("name"));
if (fs.existsSync(maybe)) {
return item.set("module", require(maybe));
} else {
/**
* Finally return a plugin that contains the error
* this will be picked up later and discarded
*/
return item.update("errors", function(errors) {
return errors.concat(e);
});
}
}
throw e;
}
}
module.exports.requirePlugin = requirePlugin;
function getFromString(string) {
/**
* We allow query strings for plugins, so always split on ?
*/
var split = string.split("?");
var outGoing = new Plugin({
moduleName: split[0],
name: split[0]
});
if (split.length > 1) {
return outGoing.update("options", function(opts) {
const parsed = parseParams(split[1]);
return opts.mergeDeep(parsed);
});
}
return outGoing;
}
================================================
FILE: packages/browser-sync/lib/public/exit.js
================================================
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function(browserSync) {
function exit() {
if (browserSync.active) {
browserSync.events.emit("service:exit");
browserSync.cleanup();
}
}
return exit;
};
================================================
FILE: packages/browser-sync/lib/public/init.ts
================================================
var _ = require("../lodash.custom");
import { merge, printErrors } from "../cli/cli-options";
/**
* @param {BrowserSync} browserSync
* @param {String} [name] - instance name
* @param {Object} pjson
* @returns {Function}
*/
module.exports = function(browserSync, name, pjson) {
return function() {
/**
* Handle new + old signatures for init.
*/
var args = require("../args")(_.toArray(arguments));
/**
* If the current instance is already running, just return an error
*/
if (browserSync.active) {
return args.cb(new Error(`Instance: ${name} is already running!`));
}
// Env specific items
args.config.version = pjson.version;
args.config.cwd = args.config.cwd || process.cwd();
const [opts, errors] = merge(args.config);
if (errors.length) {
return args.cb(new Error(printErrors(errors)));
}
return browserSync.init(opts, args.cb);
};
};
================================================
FILE: packages/browser-sync/lib/public/notify.js
================================================
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function(browserSync) {
return function(msg, timeout) {
if (msg) {
browserSync.events.emit("browser:notify", {
message: msg,
timeout: timeout || 2000,
override: true
});
}
};
};
================================================
FILE: packages/browser-sync/lib/public/pause.js
================================================
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function(browserSync) {
return function() {
browserSync.paused = true;
};
};
================================================
FILE: packages/browser-sync/lib/public/public-utils.js
================================================
"use strict";
var _ = require("../lodash.custom");
module.exports = {
/**
* Emit the internal `file:change` event
* @param {EventEmitter} emitter
* @param {string} path
* @param {boolean} [log]
*/
emitChangeEvent: function emitChangeEvent(emitter, path, log) {
emitter.emit("file:changed", {
path: path,
log: log,
namespace: "core",
event: "change"
});
},
/**
* Emit the internal `browser:reload` event
* @param {EventEmitter} emitter
*/
emitBrowserReload: function emitChangeEvent(emitter) {
emitter.emit("_browser:reload");
},
/**
* Emit the internal `stream:changed` event
* @param {EventEmitter} emitter
* @param {Array} changed
*/
emitStreamChangedEvent: function(emitter, changed) {
emitter.emit("stream:changed", { changed: changed });
},
/**
* This code handles the switch between .reload & .stream
* since 2.6.0
* @param name
* @param args
* @returns {boolean}
*/
isStreamArg: function(name, args) {
if (name === "stream") {
return true;
}
if (name !== "reload") {
return false;
}
var firstArg = args[0];
/**
* If here, it's reload with args
*/
if (_.isObject(firstArg)) {
if (!Array.isArray(firstArg) && Object.keys(firstArg).length) {
return firstArg.stream === true;
}
}
return false;
}
};
================================================
FILE: packages/browser-sync/lib/public/reload.js
================================================
"use strict";
var utils = require("../utils");
var publicUtils = require("./public-utils");
var _ = require("../lodash.custom");
var defaultConfig = require("../default-config");
var stream = require("./stream");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function(emitter) {
/**
* Inform browsers about file changes.
*
* eg: reload("core.css")
*/
function browserSyncReload(opts) {
/**
* BACKWARDS COMPATIBILITY:
* Passing an object as the only arg to the `reload`
* method with at *least* the key-value pair of {stream: true},
* was only ever used for streams support - so it's safe to check
* for that signature here and defer to the
* dedicated `.stream()` method instead.
*/
if (_.isObject(opts)) {
if (!Array.isArray(opts) && Object.keys(opts).length) {
if (opts.stream === true) {
return stream(emitter)(opts);
}
}
}
/**
* Handle single string paths such as
* reload("core.css")
*/
if (typeof opts === "string" && opts !== "undefined") {
return publicUtils.emitChangeEvent(emitter, opts, true);
}
/**
* Handle an array of file paths such as
* reload(["core.css, "ie.css"])
*/
if (Array.isArray(opts)) {
return opts.forEach(function(filepath) {
publicUtils.emitChangeEvent(emitter, filepath, true);
});
}
/**
* At this point the argument given was neither an object,
* array or string so we simply perform a reload. This is to
* allow the following syntax to work as expected
*
* reload();
*/
return publicUtils.emitBrowserReload(emitter);
}
return browserSyncReload;
};
================================================
FILE: packages/browser-sync/lib/public/resume.js
================================================
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function(browserSync) {
return function() {
browserSync.paused = false;
};
};
================================================
FILE: packages/browser-sync/lib/public/stream.js
================================================
"use strict";
var path = require("path");
var micromatch = require("micromatch");
var utils = require("./public-utils");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function(emitter) {
/**
* Return a transform/through stream that listens to file
* paths and fires internal Browsersync events.
* @param {{once: boolean, match: string|array}} [opts]
* @returns {Stream.Transform}
*/
function browserSyncThroughStream(opts) {
opts = opts || {};
var emitted = false;
var Transform = require("stream").Transform;
var reload = new Transform({ objectMode: true });
var changed = [];
reload._transform = function(file, encoding, next) {
var stream = this;
/**
* End is always called to send the current file down
* stream. Browsersync never acts upon a stream,
* we only `listen` to it.
*/
function end() {
stream.push(file); // always send the file down-stream
next();
}
/**
* If {match: } was provided, test the
* current filepath against it
*/
if (opts.match) {
if (!micromatch(file.path, opts.match, { dot: true }).length) {
return end();
}
}
/**
* if {once: true} provided, emit the reload event for the
* first file only
*/
if (opts.once === true && !emitted) {
utils.emitBrowserReload(emitter);
emitted = true;
} else {
// handle multiple
if (opts.once === true && emitted) {
} else {
if (file.path) {
emitted = true;
utils.emitChangeEvent(emitter, file.path, false);
changed.push(path.basename(file.path));
}
}
}
end();
};
/**
* When this current operation has finished, emit the
* steam:changed event so that any loggers can pick up it
* @param next
* @private
*/
reload._flush = function(next) {
if (changed.length) {
utils.emitStreamChangedEvent(emitter, changed);
}
next();
};
return reload;
}
return browserSyncThroughStream;
};
================================================
FILE: packages/browser-sync/lib/server/index.js
================================================
"use strict";
var enableDestroy = require("server-destroy");
var _ = require("../lodash.custom");
/**
* Browsersync server
* Three available modes: Snippet, Server or Proxy
*/
module.exports.plugin = function(bs) {
var debug = bs.debug;
var proxy = bs.options.get("proxy");
var type = bs.options.get("mode");
var bsServer = createServer(bs);
if (type === "server" || type === "snippet") {
debug(
"Static Server running ({magenta:%s}) ...",
bs.options.get("scheme")
);
}
if (proxy) {
debug("Proxy running, proxing: {magenta:%s}", proxy.get("target"));
}
if (bsServer) {
/**
* Allow server to be destroyed gracefully
*/
enableDestroy(bsServer.server);
/**
* Listen on the available port
*/
bsServer.server.listen(
bs.options.get("port"),
bs.options.get("listen")
);
/**
* Hack to deal with https://github.com/socketio/socket.io/issues/1602#issuecomment-224270022
*/
bs.registerCleanupTask(function() {
if (bs.io && bs.io.sockets) {
setCloseReceived(bs.io.sockets);
}
if (bs.ui && bs.ui.socket) {
setCloseReceived(bs.ui.socket);
}
});
/**
* Destroy the server on cleanup
*/
bs.registerCleanupTask(function() {
bsServer.server.destroy();
});
}
function setCloseReceived(io) {
Object.keys(io.sockets).forEach(function(key) {
_.set(
io.sockets[key],
"conn.transport.socket._closeReceived",
true
);
});
}
debug("Running mode: %s", type.toUpperCase());
return {
server: bsServer.server,
app: bsServer.app
};
};
/**
* Launch the server for serving the client JS plus static files
* @param {BrowserSync} bs
* @returns {{staticServer: (http.Server), proxyServer: (http.Server)}}
*/
function createServer(bs) {
var proxy = bs.options.get("proxy");
var server = bs.options.get("server");
if (!proxy && !server) {
return require("./snippet-server")(bs);
}
if (proxy) {
return require("./proxy-server")(bs);
}
if (server) {
return require("./static-server")(bs);
}
}
module.exports.createServer = createServer;
================================================
FILE: packages/browser-sync/lib/server/proxy-server.js
================================================
"use strict";
var httpProxy = require("http-proxy");
var utils = require("./utils");
var proxyUtils = require("./proxy-utils");
var Immutable = require("immutable");
var Map = require("immutable").Map;
var List = require("immutable").List;
/**
* Default options that are passed along to http-proxy
*/
var defaultHttpProxyOptions = Map({
/**
* This ensures targets are more likely to
* accept each request
*/
changeOrigin: true,
/**
* This handles redirects
*/
autoRewrite: true,
/**
* This allows our self-signed certs to be used for development
*/
secure: false,
ws: true
});
var defaultCookieOptions = Map({
stripDomain: true
});
var ProxyOption = Immutable.Record({
route: "",
target: "",
rewriteRules: true,
/**
* Functions to be called on proxy request
* with args [proxyReq, req, res, options]
*/
proxyReq: List([]),
/**
* Functions to be called on proxy response
* with args [proxyRes, req, res]
*/
proxyRes: List([]),
/**
* Functions to be called on proxy response
* with args [proxyReq, req, socket, options, head]
*/
proxyReqWs: List([]),
errHandler: undefined,
url: Map({}),
proxyOptions: Map(defaultHttpProxyOptions),
cookies: Map(defaultCookieOptions),
ws: false,
middleware: List([]),
reqHeaders: undefined
});
/**
* @param {BrowserSync} bs
* @param {String} scripts
* @returns {*}
*/
module.exports = function createProxyServer(bs) {
var opt = new ProxyOption().mergeDeep(bs.options.get("proxy"));
var proxy = httpProxy.createProxyServer(
opt
.get("proxyOptions")
.set("target", opt.get("target"))
.toJS()
);
var target = opt.get("target");
var proxyReq = getProxyReqFunctions(opt.get("proxyReq"), opt, bs);
var proxyRes = getProxyResFunctions(opt.get("proxyRes"), opt);
var proxyResWs = opt.get("proxyReqWs");
bs.options = bs.options.update("middleware", function(mw) {
return mw.concat({
id: "Browsersync Proxy",
route: opt.get("route"),
handle: function(req, res) {
proxy.web(req, res, {
target: target
});
}
});
});
var app = utils.getBaseApp(bs);
/**
* @type {*|{server, app}}
*/
var browserSyncServer = utils.getServer(app, bs.options);
browserSyncServer.proxy = proxy;
if (opt.get("ws")) {
// debug(`+ ws upgrade for: ${x.get("target")}`);
browserSyncServer.server.on("upgrade", function(req, socket, head) {
proxy.ws(req, socket, head);
});
}
/**
* Add any user provided functions for proxyReq, proxyReqWs and proxyRes
*/
applyFns("proxyReq", proxyReq);
applyFns("proxyRes", proxyRes);
applyFns("proxyReqWs", proxyResWs);
/**
* Handle Proxy errors
*/
proxy.on("error", function(err) {
if (typeof opt.get("errHandler") === "function") {
opt.get("errHandler").call(null, err);
}
});
/**
* Apply functions to proxy events
* @param {string} name - the name of the http-proxy event
* @param {Array} fns - functions to call on each event
*/
function applyFns(name, fns) {
if (!List.isList(fns)) fns = [fns];
proxy.on(name, function() {
var args = arguments;
fns.forEach(function(fn) {
if (typeof fn === "function") {
fn.apply(null, args);
}
});
});
}
return browserSyncServer;
};
/**
* @param resFns
* @returns {*}
*/
function getProxyResFunctions(resFns, opt) {
if (opt.getIn(["cookies", "stripDomain"])) {
return resFns.push(proxyUtils.checkCookies);
}
return resFns;
}
/**
* @param reqFns
* @returns {*}
*/
function getProxyReqFunctions(reqFns, opt, bs) {
var reqHeaders = opt.getIn(["reqHeaders"]);
if (!reqHeaders) {
return reqFns;
}
/**
* Back-compat for old `reqHeaders` option here a
* function was given that returned an object
* where key:value was header-name:header-value
* This didn't really work as it clobbered all other headers,
* but it remains for the unlucky few who used it.
*/
if (typeof reqHeaders === "function") {
var output = reqHeaders.call(bs, opt.toJS());
if (Object.keys(output).length) {
return reqFns.concat(function(proxyReq) {
Object.keys(output).forEach(function(key) {
proxyReq.setHeader(key, output[key]);
});
});
}
}
/**
* Now, if {key:value} given, set the each header
*
* eg: reqHeaders: {
* 'is-dev': 'true'
* }
*/
if (Map.isMap(reqHeaders)) {
return reqFns.concat(function(proxyReq) {
reqHeaders.forEach(function(value, key) {
proxyReq.setHeader(key, value);
});
});
}
return reqFns;
}
================================================
FILE: packages/browser-sync/lib/server/proxy-utils.js
================================================
var url = require("url");
module.exports.rewriteLinks = function(userServer) {
var host = userServer.hostname;
var string = host;
var port = userServer.port;
if (host && port) {
if (parseInt(port, 10) !== 80) {
string = host + ":" + port;
}
}
var reg = new RegExp(
// a simple, but exact match
"https?:\\\\/\\\\/" +
string +
"|" +
// following ['"] + exact
"('|\")\\/\\/" +
string +
"|" +
// exact match with optional trailing slash
"https?://" +
string +
"(?!:)(/)?" +
"|" +
// following ['"] + exact + possible multiple (imr srcset etc)
"('|\")(https?://|/|\\.)?" +
string +
"(?!:)(/)?(.*?)(?=[ ,'\"\\s])",
"g"
);
return {
match: reg,
//match: new RegExp("https?:\\\\/\\\\/" + string + "|https?://" + string + "(\/)?|('|\")(https?://|/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp("https?:\\\\?/\\\\?/" + string + "(\/)?|('|\")(https?://|\\\\?/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp('https?://' + string + '(\/)?|(\'|")(https?://|/|\\.)?' + string + '(\/)?(.*?)(?=[ ,\'"\\s])', 'g'),
//match: new RegExp("https?:\\\\/\\\\/" + string, "g"),
fn: function(req, res, match) {
var proxyUrl = req.headers["host"];
/**
* Reject subdomains
*/
if (match[0] === ".") {
return match;
}
var captured = match[0] === "'" || match[0] === '"' ? match[0] : "";
/**
* allow http https
* @type {string}
*/
var pre = "//";
if (match[0] === "'" || match[0] === '"') {
match = match.slice(1);
}
/**
* parse the url
* @type {number|*}
*/
var out = url.parse(match);
/**
* If host not set, just do a simple replace
*/
if (!out.host) {
string = string.replace(/^(\/)/, "");
return captured + match.replace(string, proxyUrl);
}
/**
* Only add trailing slash if one was
* present in the original match
*/
if (out.path === "/") {
if (match.slice(-1) === "/") {
out.path = "/";
} else {
out.path = "";
}
}
/**
* Finally append all of parsed url
*/
return [
captured,
pre,
proxyUrl,
out.path || "",
out.hash || ""
].join("");
}
};
};
/**
* Remove 'domain' from any cookies
* @param {Object} res
*/
module.exports.checkCookies = function checkCookies(res) {
if (typeof res.headers["set-cookie"] !== "undefined") {
res.headers["set-cookie"] = res.headers["set-cookie"].map(function(
item
) {
return rewriteCookies(item);
});
}
};
/**
* Remove the domain from any cookies.
* @param rawCookie
* @returns {string}
*/
function rewriteCookies(rawCookie) {
var objCookie = (function() {
// simple parse function (does not remove quotes)
var obj = {};
var pairs = rawCookie.split(/; */);
pairs.forEach(function(pair) {
var eqIndex = pair.indexOf("=");
// skip things that don't look like key=value
if (eqIndex < 0) {
return;
}
var key = pair.substr(0, eqIndex).trim();
obj[key] = pair.substr(eqIndex + 1, pair.length).trim();
});
return obj;
})();
var pairs = Object.keys(objCookie)
.filter(function(item) {
return item.toLowerCase() !== "domain";
})
.map(function(key) {
return key + "=" + objCookie[key];
});
if (rawCookie.match(/httponly/i)) {
pairs.push("HttpOnly");
}
return pairs.join("; ");
}
module.exports.rewriteCookies = rewriteCookies;
================================================
FILE: packages/browser-sync/lib/server/serve-static-wrapper.ts
================================================
export default function() {
const serveStatic = require("serve-static");
/**
* Adding a custom mime-type for wasm whilst we wait for
* the `send` package to be updated.
*/
const send = require("send");
send.mime.define({ "application/wasm": ["wasm"] });
return serveStatic;
}
================================================
FILE: packages/browser-sync/lib/server/snippet-server.js
================================================
"use strict";
var connect = require("connect");
var serverUtils = require("./utils.js");
/**
* Create a server for the snippet
* @param {BrowserSync} bs
* @param scripts
* @returns {*}
*/
module.exports = function createSnippetServer(bs, scripts) {
var app = serverUtils.getBaseApp(bs, bs.options, scripts);
return serverUtils.getServer(app, bs.options);
};
================================================
FILE: packages/browser-sync/lib/server/static-server.js
================================================
var serverUtils = require("./utils.js");
var resolve = require("path").resolve;
var utils = require("../utils.js");
var serveStatic = require("./serve-static-wrapper").default();
var serveIndex = require("serve-index");
/**
* @param {BrowserSync} bs
* @returns {*}
*/
module.exports = function createServer(bs) {
var options = bs.options;
var server = options.get("server");
var basedirs = utils.arrayify(server.get("baseDir"));
var serveStaticOptions = server.get("serveStaticOptions").toJS(); // passed to 3rd party
var _serveStatic = 0;
var _routes = 0;
bs.options = bs.options
/**
* Add directory Middleware if given in server.directory
*/
.update("middleware", function(mw) {
if (!server.get("directory")) {
return mw;
}
return mw.concat({
route: "",
handle: serveIndex(resolve(basedirs[0]), {
icons: true,
view: "details"
}),
id: "Browsersync Server Directory Middleware"
});
})
/**
* Add middleware for server.baseDir Option
*/
.update("middleware", function(mw) {
return mw.concat(
basedirs.map(function(root) {
return {
route: "",
id:
"Browsersync Server ServeStatic Middleware - " +
_serveStatic++,
handle: serveStatic(resolve(root), serveStaticOptions)
};
})
);
})
/**
* Add middleware for server.routes
*/
.update("middleware", function(mw) {
if (!server.get("routes")) {
return mw;
}
return mw.concat(
server.get("routes").map(function(root, urlPath) {
// strip trailing slash
if (urlPath[urlPath.length - 1] === "/") {
urlPath = urlPath.slice(0, -1);
}
return {
route: urlPath,
id:
"Browsersync Server Routes Middleware - " +
_routes++,
handle: serveStatic(resolve(root))
};
})
);
});
var app = serverUtils.getBaseApp(bs);
/**
* Finally, return the server + App
*/
return serverUtils.getServer(app, bs.options);
};
================================================
FILE: packages/browser-sync/lib/server/utils.js
================================================
"use strict";
var fs = require("fs");
var path = require("path");
var join = require("path").join;
var connect = require("connect");
var Immutable = require("immutable");
var http = require("http");
var https = require("https");
var Map = require("immutable").Map;
var fromJS = require("immutable").fromJS;
var List = require("immutable").List;
var snippet = require("./../snippet").utils;
var _ = require("../lodash.custom");
var serveStatic = require("./serve-static-wrapper").default();
var serveIndex = require("serve-index");
var logger = require("../logger");
var snippetUtils = require("../snippet").utils;
var lrSnippet = require("resp-modifier");
var certPath = join(__dirname, "..", "..", "certs");
function getCa(options) {
var caOption = options.getIn(["https", "ca"]);
// if not provided, use Browsersync self-signed
if (typeof caOption === "undefined") {
return fs.readFileSync(join(certPath, "server.csr"));
}
// if a string was given, read that file from disk
if (typeof caOption === "string") {
return fs.readFileSync(caOption);
}
// if an array was given, read all
if (List.isList(caOption)) {
return caOption.toArray().map(function(x) {
return fs.readFileSync(x);
});
}
}
function getKey(options) {
return fs.readFileSync(
options.getIn(["https", "key"]) || join(certPath, "server.key")
);
}
function getCert(options) {
return fs.readFileSync(
options.getIn(["https", "cert"]) || join(certPath, "server.crt")
);
}
function getHttpsServerDefaults(options) {
return fromJS({
key: getKey(options),
cert: getCert(options),
ca: getCa(options),
passphrase: options.getIn(["https", "passphrase"], "")
});
}
function getPFXDefaults(options) {
return fromJS({
pfx: fs.readFileSync(options.getIn(["https", "pfx"]))
});
}
var serverUtils = {
/**
* @param options
* @returns {{key, cert}}
*/
getHttpsOptions: function(options) {
var userOption = options.get("https");
if (Map.isMap(userOption)) {
if (userOption.has("pfx")) {
return userOption.mergeDeep(getPFXDefaults(options));
}
return userOption.mergeDeep(getHttpsServerDefaults(options));
}
return getHttpsServerDefaults(options);
},
/**
* Get either http or https server
* or use the httpModule provided in options if present
*/
getServer: function(app, options) {
return {
server: (function() {
var httpModule = serverUtils.getHttpModule(options);
if (
options.get("scheme") === "https" ||
options.get("httpModule") === "http2"
) {
var opts = serverUtils.getHttpsOptions(options);
return httpModule.createServer(opts.toJS(), app);
}
return httpModule.createServer(app);
})(),
app: app
};
},
getHttpModule: function(options) {
/**
* Users may provide a string to be used by nodes
* require lookup.
*/
var httpModule = options.get("httpModule");
if (typeof httpModule === "string") {
/**
* Note, this could throw, but let that happen as
* the error message good enough.
*/
var maybe = require.resolve(httpModule);
return require(maybe);
}
if (options.get("scheme") === "https") {
return https;
}
return http;
},
getMiddlewares: function(bs) {
var clientJs = bs.pluginManager.hook("client:js", {
port: bs.options.get("port"),
options: bs.options
});
var scripts = bs.pluginManager.get("client:script")(
bs.options.toJS(),
clientJs,
"middleware"
);
var defaultMiddlewares = [
{
id: "Browsersync HTTP Protocol",
route: require("../config").httpProtocol.path,
handle: require("../http-protocol").middleware(bs)
},
{
id: "Browsersync IE8 Support",
route: "",
handle: snippet.isOldIe(
bs.options.get("excludedFileTypes").toJS()
)
},
{
id: "Browsersync Response Modifier",
route: "",
handle: serverUtils.getSnippetMiddleware(bs)
},
{
id: "Browsersync Client - versioned",
route: bs.options.getIn(["scriptPaths", "versioned"]),
handle: scripts
},
{
id: "Browsersync Client",
route: bs.options.getIn(["scriptPaths", "path"]),
handle: scripts
}
];
/**
* Add cors middleware to the front of the stack
* if a user provided a 'cors' flag
*/
if (bs.options.get("cors")) {
defaultMiddlewares.unshift({
id: "Browsersync CORS support",
route: "",
handle: serverUtils.getCorsMiddlewware()
});
}
/**
* Add connect-history-api-fallback if 'single' argument given
*/
if (bs.options.get("single")) {
defaultMiddlewares.unshift({
id: "Browsersync SPA support",
route: "",
handle: require("connect-history-api-fallback")()
});
}
/**
* Add serve static middleware
*/
if (bs.options.get("serveStatic")) {
var ssMiddlewares = serverUtils.getServeStaticMiddlewares(
bs.options.get("serveStatic"),
bs.options.get("serveStaticOptions", Immutable.Map({})).toJS()
);
var withErrors = ssMiddlewares.filter(function(x) {
return x.get("errors").size > 0;
});
var withoutErrors = ssMiddlewares.filter(function(x) {
return x.get("errors").size === 0;
});
if (withErrors.size) {
withErrors.forEach(function(item) {
logger.logger.error(
"%s %s",
chalk.red("Warning!"),
item.getIn(["errors", 0, "data", "message"])
);
});
}
if (withoutErrors.size) {
withoutErrors.forEach(function(item) {
defaultMiddlewares.push.apply(
defaultMiddlewares,
item.get("items").toJS()
);
});
}
}
/**
* Add user-provided middlewares
*/
var userMiddlewares = bs.options
.get("middleware")
.map(normaliseMiddleware)
.toArray();
var beforeMiddlewares = userMiddlewares.filter(function(x) {
return x.override;
});
var afterMiddlewares = userMiddlewares
.filter(function(x) {
return !x.override;
})
.concat(
bs.options.get("mode") !== "proxy" &&
userMiddlewares.length === 0 && {
id: "Browsersync 404/index support",
route: "",
handle: serveIndex(bs.options.get("cwd"), {
icons: true,
view: "details"
})
}
);
const mwStack = []
.concat(beforeMiddlewares, defaultMiddlewares, afterMiddlewares)
.filter(Boolean);
return mwStack;
function normaliseMiddleware(item) {
/**
* Object given in options, which
* ended up being a Map
*/
if (Map.isMap(item)) {
return item.toJS();
}
/**
* Single function
*/
if (typeof item === "function") {
return {
route: "",
handle: item
};
}
/**
* Plain obj
*/
if (item.route !== undefined && item.handle) {
return item;
}
}
},
getBaseApp: function(bs) {
var app = connect();
var middlewares = serverUtils.getMiddlewares(bs);
/**
* Add all internal middlewares
*/
middlewares.forEach(function(item) {
app.stack.push(item);
});
return app;
},
getSnippetMiddleware: function(bs) {
var rules = [];
var blacklist = List([])
.concat(bs.options.getIn(["snippetOptions", "ignorePaths"]))
.concat(bs.options.getIn(["snippetOptions", "blacklist"]))
.filter(Boolean);
var whitelist = List([]).concat(
bs.options.getIn(["snippetOptions", "whitelist"])
);
// Snippet
if (bs.options.get("snippet")) {
rules.push(
snippetUtils.getRegex(
bs.options.get("snippet"),
bs.options.get("snippetOptions")
)
);
}
// User
bs.options.get("rewriteRules").forEach(function(rule) {
if (Map.isMap(rule)) {
rules.push(rule.toJS());
}
if (_.isPlainObject(rule)) {
rules.push(rule);
}
});
// Proxy
if (bs.options.get("proxy")) {
var proxyRule = require("./proxy-utils").rewriteLinks(
bs.options.getIn(["proxy", "url"]).toJS()
);
rules.push(proxyRule);
}
var lr = lrSnippet.create({
rules: rules,
blacklist: blacklist.toArray(),
whitelist: whitelist.toArray()
});
return lr.middleware;
},
getCorsMiddlewware: function() {
return function(req, res, next) {
// Website you wish to allow to connect
res.setHeader("Access-Control-Allow-Origin", "*");
// Request methods you wish to allow
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS, PUT, PATCH, DELETE"
);
// Request headers you wish to allow
res.setHeader(
"Access-Control-Allow-Headers",
"X-Requested-With,content-type"
);
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader("Access-Control-Allow-Credentials", true);
next();
};
},
/**
* @param ssOption
* @param serveStaticOptions
* @returns {*}
*/
getServeStaticMiddlewares: function(ssOption, serveStaticOptions) {
return ssOption.map(function(dir, i) {
/**
* When a user gives a plain string only, eg:
* serveStatic: ['./temp']
* ->
* This means a middleware will be created with
* route: ''
* handle: serveStatic('./temp', options)
*/
if (_.isString(dir)) {
return getFromString(dir);
}
/**
* If a user gave an object eg:
* serveStatic: [{route: "", dir: ["test", "./tmp"]}]
* ->
* This means we need to create a middle for each route + dir combo
*/
if (Immutable.Map.isMap(dir)) {
return getFromMap(dir, i);
}
/**
* At this point, an item in the serveStatic array was not a string
* or an object so we return an error that can be logged
*/
return fromJS({
items: [],
errors: [
{
type: "Invalid Type",
data: {
message:
"Only strings and Objects (with route+dir) are supported for the ServeStatic option"
}
}
]
});
});
/**
* @param {string} x
* @returns {string}
*/
function getRoute(x) {
if (x === "") return "";
return x[0] === "/" ? x : "/" + x;
}
/**
* @param dir
* @returns {Map}
*/
function getFromString(dir) {
return fromJS({
items: [
{
route: "",
handle: serveStatic(dir, serveStaticOptions)
}
],
errors: []
});
}
/**
* @param dir
* @returns {Map}
*/
function getFromMap(dir) {
var ssOptions = (function() {
if (dir.get("options")) {
return dir.get("options").toJS();
}
return {};
})();
var route = Immutable.List([])
.concat(dir.get("route"))
.filter(_.isString);
var _dir = Immutable.List([])
.concat(dir.get("dir"))
.filter(_.isString);
if (_dir.size === 0) {
return fromJS({
items: [],
errors: [
{
type: "Invalid Object",
data: {
message:
"Serve Static requires a 'dir' property when using an Object"
}
}
]
});
}
var ssItems = (function() {
/**
* iterate over every 'route' item
* @type {Immutable.List|Immutable.List<*>|Immutable.List|*}
*/
var routeItems = (function() {
/**
* If no 'route' was given, assume we want to match all
* paths
*/
if (route.size === 0) {
return _dir.map(function(dirString) {
return Map({
route: "",
dir: dirString
});
});
}
return route.reduce(function(acc, routeString) {
/**
* For each 'route' item, also iterate through 'dirs'
* @type {Immutable.Iterable}
*/
var perDir = _dir.map(function(dirString) {
return Map({
route: getRoute(routeString),
dir: dirString
});
});
return acc.concat(perDir);
}, List([]));
})();
/**
* Now create a serverStatic Middleware for each item
*/
return routeItems.map(function(routeItem) {
return routeItem.merge({
handle: serveStatic(routeItem.get("dir"), ssOptions)
});
});
})();
return fromJS({
items: ssItems,
errors: []
});
}
}
};
module.exports = serverUtils;
================================================
FILE: packages/browser-sync/lib/snippet.js
================================================
"use strict";
var connectUtils = require("./connect-utils");
var config = require("./config");
var lrSnippet = require("resp-modifier");
var path = require("path");
var _ = require("./lodash.custom");
var utils = require("./utils");
var fs = require("fs");
var path = require("path");
/**
* Utils for snippet injection
*/
var snippetUtils = {
/**
* @param {String} url
* @param {Array} excludeList
* @returns {boolean}
*/
isExcluded: function(url, excludeList) {
var extension = path.extname(url);
if (extension) {
if (~url.indexOf("?")) {
return true;
}
extension = extension.slice(1);
return _.includes(excludeList, extension);
}
return false;
},
/**
* @param {String} snippet
* @param {Object} options
* @returns {{match: RegExp, fn: Function}}
*/
getRegex: function(snippet, options) {
var fn = options.getIn(["rule", "fn"]);
return {
match: options.getIn(["rule", "match"]),
fn: function(req, res, match) {
return fn.apply(null, [snippet, match]);
},
once: true,
id: "bs-snippet"
};
},
getSnippetMiddleware: function(snippet, options, rewriteRules) {
return lrSnippet.create(
snippetUtils.getRules(snippet, options, rewriteRules)
);
},
getRules: function(snippet, options, rewriteRules) {
var rules = [snippetUtils.getRegex(snippet, options)];
if (rewriteRules) {
rules = rules.concat(rewriteRules);
}
return {
rules: rules,
blacklist: utils.arrayify(options.get("blacklist")),
whitelist: utils.arrayify(options.get("whitelist"))
};
},
/**
* @param {Object} req
* @param {Array} [excludeList]
* @returns {Object}
*/
isOldIe: function(excludeList) {
return function(req, res, next) {
var ua = req.headers["user-agent"];
var match = /MSIE (\d)\.\d/.exec(ua);
if (match) {
if (parseInt(match[1], 10) < 9) {
if (!snippetUtils.isExcluded(req.url, excludeList)) {
req.headers["accept"] = "text/html";
}
}
}
next();
};
},
/**
* @param {Number} port
* @param {BrowserSync.options} options
* @returns {String}
*/
getClientJs: function(port, options) {
return () => {
const script = options.get("minify") ? "index.js" : "index.js";
const client = fs.readFileSync(
require.resolve("browser-sync-client/dist/" + script),
"utf8"
);
return [connectUtils.socketConnector(options), client].join(";\n");
};
}
};
module.exports.utils = snippetUtils;
================================================
FILE: packages/browser-sync/lib/sockets.ts
================================================
import {Server} from "socket.io";
import * as utils from "./server/utils";
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
export function plugin(server, clientEvents, bs) {
return exports.init(server, clientEvents, bs);
}
/**
* @param {http.Server} server
* @param clientEvents
* @param {BrowserSync} bs
*/
export function init(server, clientEvents, bs) {
var emitter = bs.events;
var socketConfig = bs.options.get("socket").toJS();
if (
bs.options.get("mode") === "proxy" &&
bs.options.getIn(["proxy", "ws"])
) {
server = utils.getServer(null, bs.options).server;
server.listen(bs.options.getIn(["socket", "port"]));
bs.registerCleanupTask(function() {
server.close();
});
}
var socketIoConfig = socketConfig.socketIoOptions;
socketIoConfig.path = socketConfig.path;
const io = new Server();
io.attach(server, {
...socketIoConfig,
pingTimeout: socketConfig.clients.heartbeatTimeout,
cors: {
credentials: true,
"origin": (origin, cb) => {
return cb(null, origin)
},
}
});
io.of(socketConfig.namespace).on('connection', (socket) => {
handleConnection(socket);
});
/**
* Handle each new connection
* @param {Object} client
*/
function handleConnection(client) {
// set ghostmode callbacks
if (bs.options.get("ghostMode")) {
addGhostMode(client);
}
client.emit("connection", bs.options.toJS()); //todo - trim the amount of options sent to clients
emitter.emit("client:connected", {
ua: client.handshake.headers["user-agent"]
});
}
/**
* @param client
*/
function addGhostMode(client) {
clientEvents.forEach(addEvent);
function addEvent(event) {
client.on(event, data => {
client.broadcast.emit(event, data);
});
}
}
// @ts-ignore
io.sockets = io.of(socketConfig.namespace)
return io;
}
================================================
FILE: packages/browser-sync/lib/tunnel.js
================================================
"use strict";
var _ = require("./lodash.custom");
var utils = require("util");
/**
* @param {BrowserSync} bs
* @param {Function} cb
*/
module.exports = function(bs, cb) {
var localtunnel;
try {
localtunnel = require("localtunnel");
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
var error = new Error("Could not find package `localtunnel`. From Browsersync version 3.0 you'll need to install this manually.");
error.code = e.code;
return cb(error);
}
return cb(e);
}
var opts = {};
var options = bs.options;
var port = options.get("port");
if (_.isString(options.get("tunnel"))) {
opts.subdomain = options.get("tunnel");
}
bs.debug("Requesting a tunnel connection on port: {magenta:%s}", port);
bs.debug(
"Requesting a tunnel connection with options: {magenta:%s}",
utils.inspect(opts)
);
localtunnel(port, opts, function(err, tunnel) {
if (err) {
return cb(err);
}
tunnel.on("error", function(err) {
bs.logger.info("Localtunnel issue: " + err.message);
bs.logger.info(
"Oops! The localtunnel appears to have disconnected. Reconnecting..."
);
});
return cb(null, tunnel);
});
};
================================================
FILE: packages/browser-sync/lib/types.ts
================================================
import * as url from "url";
import { Map } from "immutable";
export type ServerIncoming = string | string[] | IServerOption;
export interface IServerOption {
baseDir: string[];
index?: string;
directory?: boolean;
serveStaticOptions?: any;
routes?: { [route: string]: string };
middleware?: MiddlewareInput;
}
export type MiddlewareInput = Function | Function[] | Middleware | Middleware[];
export interface Middleware {
route: string;
id?: string;
handle: Function;
}
export type BrowsersyncProxyIncoming = string | BrowsersyncProxy;
export interface BrowsersyncProxy {
target: string;
url: Map;
middleware?: MiddlewareInput;
}
export type PortsOption = {
min: number | null;
max: number | null;
};
export type FilesObject = { match: string[]; fn?: Function; options?: any };
export type FilesNamespace = { globs: string[]; objs: FilesObject[] };
export type FilesNamespaces = { [name: string]: FilesNamespace };
================================================
FILE: packages/browser-sync/lib/utils.ts
================================================
import { BsTempOptions } from "./cli/cli-options";
import * as devIp from "dev-ip";
import * as portScanner from "portscanner";
import * as path from "path";
import * as UAParser from "ua-parser-js";
import * as Immutable from "immutable";
import { List } from "immutable";
const _ = require("./lodash.custom");
const parser = new UAParser();
/**
* @param {Object} options
* @returns {String|boolean} - the IP address
* @param devIp
*/
export function getHostIp(options: BsTempOptions, devIp: string[]) {
if (options) {
var host = options.get("host");
if (host && host !== "localhost") {
return host;
}
if (options.get("detect") === false || !devIp.length) {
return false;
}
}
return devIp.length ? devIp[0] : false;
}
/**
* Set URL Options
*/
export function getUrlOptions(options: BsTempOptions): Map {
const scheme = options.get("scheme");
const port = options.get("port");
const urls: { [index: string]: string } = {};
const listen = options.get("listen");
if (options.get("online") === false || listen) {
const host = listen || "localhost";
urls.local = getUrl(`${scheme}://${host}:${port}`, options);
return Immutable.fromJS(urls);
}
const fn: typeof getHostIp = exports.getHostIp;
const external = hostnameSuffix(fn(options, devIp()), options);
const localhost = hostnameSuffix("localhost", options);
return Immutable.fromJS(getUrls(external, localhost, scheme, options));
}
/**
* Append a start path if given in options
* @param {String} url
* @param {Object} options
* @returns {String}
*/
export function getUrl(url: string, options: BsTempOptions) {
var prefix = "/";
var startPath = options.get("startPath");
if (startPath) {
if (startPath.charAt(0) === "/") {
prefix = "";
}
url = url + prefix + startPath;
}
return url;
}
/**
* @param {String} external
* @param {String} local
* @param {String} scheme
* @param {Object} options
* @returns {{local: string, external: string}}
*/
export function getUrls(external, local, scheme, options) {
var urls: { [index: string]: string } = {
local: getUrl(_makeUrl(scheme, local, options.get("port")), options)
};
if (external !== local) {
urls.external = getUrl(
_makeUrl(scheme, external, options.get("port")),
options
);
}
return urls;
}
/**
* @param {String} scheme
* @param {String} host
* @param {Number} port
* @returns {String}
* @private
*/
export function _makeUrl(scheme, host, port) {
return scheme + "://" + host + ":" + port;
}
export type PortLookupCb = (error: null | Error, port: number) => void;
/**
* Get ports
* @param {Object} options
* @param {Function} cb
*/
export function getPorts(options: BsTempOptions, cb: PortLookupCb) {
var port = options.get("port");
var ports = options.get("ports"); // backwards compatibility
var host = options.get("listen", "localhost"); // backwards compatibility
var max;
if (ports) {
port = ports.get("min");
max = ports.get("max") || null;
}
var fn: typeof getPort = exports.getPort;
fn(host, port, max, cb);
}
export function getPort(
host: string,
port: number | string,
max: number | string | null,
cb: PortLookupCb
) {
portScanner.findAPortNotInUse(
port,
max,
{
host: host,
timeout: 1000
},
cb
);
}
/**
* @param {String} ua
* @returns {Object}
*/
export function getUaString(ua) {
return parser.setUA(ua).getBrowser();
}
/**
* Open the page in browser
* @param {String} url
* @param {Object} options
* @param {BrowserSync} bs
*/
export function openBrowser(url, options, bs) {
const open = options.get("open");
const browser = options.get("browser");
if (_.isString(open)) {
if (options.getIn(["urls", open])) {
url = options.getIn(["urls", open]);
}
}
const fn: typeof opnWrapper = exports.opnWrapper;
if (open) {
if (browser !== "default") {
if (isList(browser)) {
browser.forEach(function(browser) {
fn(url, browser, bs);
});
} else {
fn(url, browser, bs); // single
}
} else {
fn(url, null, bs);
}
}
}
/**
* Wrapper for opn module
* @param url
* @param name
* @param bs
*/
export function opnWrapper(url, name, bs) {
var options = (function() {
if (_.isString(name)) {
return { app: name };
}
if (Immutable.Map.isMap(name)) {
return name.toJS();
}
return {};
})();
var opn = require("opn");
opn(url, options).catch(function() {
bs.events.emit("browser:error");
});
}
/**
* @param {Boolean} kill
* @param {String|Error} [errMessage]
* @param {Function} [cb]
*/
export function fail(kill, errMessage, cb) {
if (kill) {
if (_.isFunction(cb)) {
if (errMessage.message) {
// Is this an error object?
cb(errMessage);
} else {
cb(new Error(errMessage));
}
}
process.exit(1);
}
}
/**
* hostnameSuffix
* @param {String} host
* @param {Object} options
* @returns {String}
*/
export function hostnameSuffix(host, options) {
var suffix = options.get("hostnameSuffix");
if (suffix) {
return host + suffix;
}
return host;
}
/**
* Determine if an array of file paths will cause a full page reload.
* @param {Array} needles - filepath such as ["core.css", "index.html"]
* @param {Array} haystack
* @returns {Boolean}
*/
export function willCauseReload(needles, haystack) {
return needles.some(function(needle) {
return !_.includes(haystack, path.extname(needle).replace(".", ""));
});
}
export const isList = Immutable.List.isList;
export const isMap = Immutable.Map.isMap;
/**
* @param {Map} options
* @returns {Array}
*/
export function getConfigErrors(options) {
var messages = require("./config").errors;
var errors = [];
if (options.get("server") && options.get("proxy")) {
errors.push(messages["server+proxy"]);
}
return errors;
}
/**
* @param {Map} options
* @param {Function} [cb]
*/
export function verifyConfig(options, cb) {
var errors = getConfigErrors(options);
if (errors.length) {
fail(true, errors.join("\n"), cb);
return false;
}
return true;
}
export function eachSeries(arr, iterator, callback) {
callback = callback || function() {};
var completed = 0;
var iterate = function() {
iterator(arr[completed], function(err) {
if (err) {
callback(err);
callback = function() {};
} else {
++completed;
if (completed >= arr.length) {
callback();
} else {
iterate();
}
}
});
};
iterate();
}
/**
* @param {Immutable.List|Array|String} incoming
* @returns {Array}
*/
export function arrayify(incoming) {
if (List.isList(incoming)) {
return incoming.toArray();
}
return [].concat(incoming).filter(Boolean);
}
export function defaultCallback(err?: Error) {
if (err && err.message) {
console.error(err.message);
}
}
export const portscanner = portScanner;
export const connect = require("connect");
export const serveStatic = require("./server/serve-static-wrapper").default();
export const easyExtender = require("easy-extender");
export { UAParser, devIp };
/**
* Just for backwards compat around old argument styles
*/
export function parseParams(search: string): Record {
const params = new URLSearchParams(search);
const parsed = Object.create(null);
for (let [key, value] of params) {
let nextKey = key;
let arrayType = false;
if (nextKey.slice(-2) === "[]") {
nextKey = key.slice(0, -2);
arrayType = true;
}
const curr = parsed[nextKey];
if (curr && Array.isArray(curr)) {
curr.push(value);
} else if (curr) {
// if it already exists, but is not already an array, upgrade to array
parsed[nextKey] = [curr, value];
} else {
// otherwise create the original value
if (arrayType) {
parsed[nextKey] = [value];
} else {
parsed[nextKey] = value;
}
}
}
return parsed;
}
/**
* Also for backwards compat around old argument styles
*/
export function serializeParams(args: Record = {}): URLSearchParams {
const output = new URLSearchParams();
for (let [key, value] of Object.entries(args)) {
if (Array.isArray(value)) {
for (let valueElement of value) {
output.append(key, valueElement);
}
} else {
output.append(key, String(value));
}
}
return output;
}
================================================
FILE: packages/browser-sync/package.json
================================================
{
"name": "browser-sync",
"description": "Live CSS Reload & Browser Syncing",
"version": "3.0.4",
"homepage": "https://browsersync.io/",
"author": {
"name": "Shane Osbourne"
},
"repository": "BrowserSync/browser-sync",
"license": "Apache-2.0",
"main": "dist/index.js",
"bin": "dist/bin.js",
"files": [
"dist",
"certs",
"templates",
"cli-options",
"client/dist"
],
"engines": {
"node": ">= 8.0.0"
},
"scripts": {
"build": "npm run build:server",
"build:server": "tsc",
"build:watch": "tsc --watch",
"env": "node ./test/env.js",
"lodash": "lodash include=isUndefined,isFunction,toArray,includes,union,each,isString,merge,isObject,set exports=node",
"prepublishOnly": "npm run build",
"prettier": "prettier 'lib/**/*' 'examples/*' 'test/specs/**/*.js' --tab-width 4",
"prettier:fix": "npm run prettier -- --write",
"test": "npm run build && npm run env && npm run unit",
"unit": "mocha --recursive test/specs --timeout 10000 --bail --exit",
"watch": "npm run build && npm run serve:fixtures",
"serve:fixtures": "node dist/bin test/fixtures -w --no-open"
},
"dependencies": {
"browser-sync-client": "^3.0.4",
"browser-sync-ui": "^3.0.4",
"bs-recipes": "1.3.4",
"chalk": "4.1.2",
"chokidar": "^3.5.1",
"connect": "3.6.6",
"connect-history-api-fallback": "^1",
"dev-ip": "^1.0.1",
"easy-extender": "^2.3.4",
"eazy-logger": "^4.1.0",
"etag": "^1.8.1",
"fresh": "^0.5.2",
"fs-extra": "3.0.1",
"http-proxy": "^1.18.1",
"immutable": "^3",
"micromatch": "^4.0.8",
"opn": "5.3.0",
"portscanner": "2.2.0",
"raw-body": "^2.3.2",
"resp-modifier": "6.0.2",
"rx": "4.1.0",
"send": "^0.19.0",
"serve-index": "^1.9.1",
"serve-static": "^1.16.2",
"server-destroy": "1.0.1",
"socket.io": "^4.4.1",
"ua-parser-js": "^1.0.33",
"yargs": "^17.3.1"
},
"devDependencies": {
"@types/node": "^8",
"bs-snippet-injector": "^2.0.1",
"chai": "^3",
"generate-changelog": "^1.7.0",
"graceful-fs": "4.1.9",
"http2": "^3.3.6",
"mocha": "^10.2.0",
"prettier": "^1.9.2",
"q": "1.4.1",
"request": "^2",
"requirejs": "^2.3.5",
"rimraf": "2.5.4",
"sinon": "^1",
"socket.io-client": "^2.4.0",
"source-map-support": "^0.5.0",
"strip-ansi": "^6.0.1",
"supertest": "^3",
"typescript": "^4.6.2",
"vinyl": "1.2.0"
},
"keywords": [
"browser sync",
"css",
"live reload",
"sync"
]
}
================================================
FILE: packages/browser-sync/readme.md
================================================
Keep multiple browsers & devices in sync when building websites.
Follow @Browsersync on twitter for news & updates.
## Features
Please visit [browsersync.io](https://browsersync.io) for a full run-down of features
## Requirements
Browsersync works by injecting an asynchronous script tag (``) right after the `` tag
during initial request. In order for this to work properly the `` tag must be present. Alternatively you
can provide a custom rule for the snippet using [snippetOptions](https://www.browsersync.io/docs/options/#option-snippetOptions)
## Upgrading from 1.x to 2.x ?
Providing you haven't accessed any internal properties, everything will just work as
there are no breaking changes to the public API. Internally however, we now use an
immutable data structure for storing/retrieving options. So whereas before you could access urls like this...
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.urls.local);
});
```
... you now access them in the following way:
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.getIn(["urls", "local"]));
});
```
## Install and trouble shooting
[browsersync.io docs](https://browsersync.io)
## Integrations / recipes
[Browsersync recipes](https://github.com/Browsersync/recipes)
## Support
If you've found Browser-sync useful and would like to contribute to its continued development & support, please feel free to send a donation of any size - it would be greatly appreciated!
[Support via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=shakyshane%40gmail%2ecom&lc=US&item_name=browser%2dsync)
## Supported by
Originally supported by [JH](https://www.wearejh.com) - they provided financial support as well as access to a professional designer to help with Branding.
Apache 2
Copyright (c) 2021 Shane Osbourne
================================================
FILE: packages/browser-sync/templates/cli-template.js
================================================
/*
|--------------------------------------------------------------------------
| Browser-sync config file
|--------------------------------------------------------------------------
|
| For up-to-date information about the options:
| http://www.browsersync.io/docs/options/
|
| There are more options than you see here, these are just the ones that are
| set internally. See the website for more info.
|
|
*/
module.exports = //OPTS;
================================================
FILE: packages/browser-sync/templates/connector.tmpl
================================================
window.___browserSync___ = {};
___browserSync___.socketConfig = %config%;
___browserSync___.socketUrl = %url%;
___browserSync___.options = %options%;
if (location.protocol == "https:" && /^http:/.test(___browserSync___.socketUrl)) {
___browserSync___.socketUrl = ___browserSync___.socketUrl.replace(/^http:/, "https:");
}
================================================
FILE: packages/browser-sync/templates/script-tags-simple.html
================================================
================================================
FILE: packages/browser-sync/templates/script-tags.html
================================================
================================================
FILE: packages/browser-sync/test/env.js
================================================
process.env.TESTING = true;
================================================
FILE: packages/browser-sync/test/fixtures/.tmp/temp.css
================================================
body {
background: red;
}
================================================
FILE: packages/browser-sync/test/fixtures/alt/index.htm
================================================
Test HTML Page
Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet.
Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.