Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Vue preset #484

Merged
merged 15 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion packages/neutrino/src/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Config = require('webpack-chain');
const merge = require('deepmerge');
const Future = require('fluture');
const { tail, equals } = require('ramda');
const mitt = require('mitt');
const {
defaultTo, is, map, omit, prop
Expand All @@ -26,14 +27,28 @@ const pathOptions = [

// getOptions :: Object? -> IO Object
const getOptions = (opts = {}) => {
let fileExtensions = new Set(['js', 'jsx', 'vue', 'ts', 'mjs']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this to moduleExtensions so it's clear we are only dealing with module code instead of things like images and stylesheets.

const options = merge({
env: {
NODE_ENV: 'development'
},
debug: false,
quiet: false
quiet: false,
extensions: fileExtensions
}, opts);

Object.defineProperty(options, 'extensions', {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eliperelman Let me know if this is the way you wanted it to be done. Once we've agreed on a way, I could update the rest neutrino to use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in IRC, this can just be a normal property, and consumers can access the Set directly. We can also expose a method for generating a regex on the fly, but we don't want to limit this property to only creating regexes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing, a user should be able to pass this as an array to Neutrino options, which when merged using mergeOptions or whatever, can be merged with the Set values:

module.exports = {
  options: {
    extensions: ['vue']
  }
};

// ...

new Set([...this.options.extensions, ...extensions])

When merging we should also probably normalize the values to ensure they don't contain dots.

enumerable: true,
get() {
return [...fileExtensions];
},
set(extensions) {
const newExtensions = extensions.map(ext => equals(ext[0], '.') ? tail(ext) : ext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be simplified to:

const newExtensions = extensions.map(ext => ext.replace('.', ''));


fileExtensions = [...new Set(newExtensions)];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the original value of fileExtensions is a Set, but here we spread it into an array. We can keep this as a Set here, and only spread to array during get:

fileExtensions = new Set(newExtensions);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combining this with my previous comment, this can be one-lined into:

fileExtensions = new Set(extensions.map(ext => ext.replace('.', '')));

}
});

Object
.keys(options.env)
.forEach(env => process.env[env] = options.env[env]);
Expand Down Expand Up @@ -87,6 +102,10 @@ class Api {
this.config = new Config();
}

regexFromExtensions(extensions = this.options.extensions) {
return new RegExp(`.(${extensions.join('|')})$`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

}

emit(...args) {
return this.emitter.emit(...args);
}
Expand Down
1 change: 1 addition & 0 deletions packages/vue/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/test/
256 changes: 256 additions & 0 deletions packages/vue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
# Neutrino Vue Preset

[![NPM version][npm-image]][npm-url]
[![NPM downloads][npm-downloads]][npm-url]
[![Join the Neutrino community on Spectrum][spectrum-image]][spectrum-url]

## Features

- Zero upfront configuration necessary to start developing and building a Vue web app
- Modern Babel compilation.
- Extends from [@neutrinojs/web](https://neutrino.js.org/packages/web)
- Modern Babel compilation supporting ES modules, last 2 major browser versions, async functions, and dynamic imports
- Webpack loaders for importing HTML, CSS, images, icons, fonts, and web workers
- Webpack Dev Server during development
- Automatic creation of HTML pages, no templating necessary
- Hot Module Replacement support
- Tree-shaking to create smaller bundles
- Production-optimized bundles with Babili minification, easy chunking, and scope-hoisted modules for faster execution
- Easily extensible to customize your project as needed

## Requirements

- Node.js v6.10+
- Yarn or npm client
- Neutrino v7

## Installation

`@neutrinojs/vue` can be installed via the Yarn or npm clients. Inside your project, make sure
`neutrino` and `@neutrinojs/vue` are development dependencies. You will also need Vue for actual
Vue development.

#### Yarn

```bash
❯ yarn add --dev neutrino @neutrinojs/vue
❯ yarn add vue
```

#### npm

```bash
❯ npm install --save-dev neutrino @neutrinojs/vue
❯ npm install --save vue


## Project Layout

`@neutrinojs/vue` follows the standard [project layout](https://neutrino.js.org/project-layout) specified by Neutrino. This
means that by default all project source code should live in a directory named `src` in the root of the
project. This includes JavaScript files, CSS stylesheets, images, and any other assets that would be available
to import your compiled project.

## Quickstart

After installing Neutrino and the Vue preset, add a new directory named `src` in the root of the project, with
two files `index.js` and `App.vue` in it.

```bash
❯ mkdir src && touch src/index.js && touch src/App.vue
```

This Vue preset exposes an element in the page with an ID of `root` to which you can mount your application. Edit
your `src/index.js` file with the following:

```js
import Vue from 'vue';
import App from './App.vue';

new Vue({
el: '#root',
render: (h) => h(App),
});
```

Next, edit your `src/App.vue` with the following:

```html
<script>
export default {
name: 'App',
data() {
return {};
}
};
</script>

<template>
<div>
<h1>Hello world!</h1>
</div>
</template>
```

Now edit your project's package.json to add commands for starting and building the application:

```json
{
"scripts": {
"start": "neutrino start --use @neutrinojs/vue",
"build": "neutrino build --use @neutrinojs/vue"
}
}
```

If you are using `.neutrinorc.js`, add this preset to your use array instead of `--use` flags:

```js
module.exports = {
use: ['@neutrinojs/vue']
};
```

#### Yarn

```bash
❯ yarn start
✔ Development server running on: http://localhost:5000
✔ Build completed
```

#### npm

```bash
❯ npm start
✔ Development server running on: http://localhost:5000
✔ Build completed
```

Start the app, then open a browser to the address in the console:

## Building

`@neutrinojs/vue` builds static assets to the `build` directory by default when running `neutrino build`. Using
the quick start example above as a reference:

```bash
❯ yarn build

✔ Building project completed
Hash: b26ff013b5a2d5f7b824
Version: webpack 3.5.6
Time: 9773ms
Asset Size Chunks Chunk Names
index.dfbad882ab3d86bfd747.js 181 kB index [emitted] index
runtime.3d9f9d2453f192a2b10f.js 1.51 kB runtime [emitted] runtime
index.html 846 bytes [emitted]
✨ Done in 14.62s.
```

You can either serve or deploy the contents of this `build` directory as a static site.

## Static assets

If you wish to copy files to the build directory that are not imported from application code, you can place
them in a directory within `src` called `static`. All files in this directory will be copied from `src/static`
to `build/static`. To change this behavior, specify your own patterns with
[@neutrinojs/copy](https://neutrino.js.org/packages/copy).

## Paths

The `@neutrinojs/web` preset loads assets relative to the path of your application by setting Webpack's
[`output.publicPath`](https://webpack.js.org/configuration/output/#output-publicpath) to `./`. If you wish to load
assets instead from a CDN, or if you wish to change to an absolute path for your application, customize your build to
override `output.publicPath`. See the [Customizing](#Customizing) section below.


## Preset options

You can provide custom options and have them merged with this preset's default options to easily affect how this
preset builds. You can modify Vue preset settings from `.neutrinorc.js` by overriding with an options object. Use
an array pair instead of a string to supply these options in `.neutrinorc.js`.

The following shows how you can pass an options object to the Vue preset and override its options. See the
[Web documentation](https://neutrino.js.org/packages/web#preset-options) for specific options you can override with this object.

```js
module.exports = {
use: [
['@neutrinojs/vue', {
/* preset options */

// Example: disable Hot Module Replacement
hot: false,

// Example: change the page title
html: {
title: 'Epic Vue App'
},

// Target specific browsers with babel-preset-env
targets: {
browsers: [
'last 1 Chrome versions',
'last 1 Firefox versions'
]
},

// Add additional Babel plugins, presets, or env options
babel: {
// Override options for babel-preset-env:
presets: [
['babel-preset-env', {
modules: false,
useBuiltIns: true,
exclude: ['transform-regenerator', 'transform-async-to-generator'],
}]
]
}
}]
]
};
```

## Customizing

To override the build configuration, start with the documentation on [customization](https://neutrino.js.org/customization).
`@neutrinojs/vue` does not use any additional named rules, loaders, or plugins that aren't already in use by the
Web preset. See the [Web documentation customization](https://neutrino.js.org/packages/web#customizing)
for preset-specific configuration to override.

### Advanced configuration

By following the [customization guide](https://neutrino.js.org/customization) and knowing the rule, loader, and plugin IDs from
`@neutrinojs/web`, you can override and augment the build by providing a function to your `.neutrinorc.js` use
array. You can also make these changes from the Neutrino API in custom middleware.

#### Vendoring

By defining an entry point named `vendor` you can split out external dependencies into a chunk separate
from your application code.

_Example: Put Vue into a separate "vendor" chunk:_

```js
module.exports = {
use: [
'@neutrinojs/vue',
(neutrino) => neutrino.config
.entry('vendor')
.add('vue')
]
};
```

## Contributing

This preset is part of the [neutrino-dev](https://github.com/mozilla-neutrino/neutrino-dev) repository, a monorepo
containing all resources for developing Neutrino and its core presets and middleware. Follow the
[contributing guide](https://neutrino.js.org/contributing) for details.

[npm-image]: https://img.shields.io/npm/v/@neutrinojs/vue.svg
[npm-downloads]: https://img.shields.io/npm/dt/@neutrinojs/vue.svg
[npm-url]: https://npmjs.org/package/@neutrinojs/vue
[spectrum-image]: https://withspectrum.github.io/badge/badge.svg
[spectrum-url]: https://spectrum.chat/neutrino
57 changes: 57 additions & 0 deletions packages/vue/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const web = require('@neutrinojs/web');
const path = require('path');
const merge = require('deepmerge');
const loaderMerge = require('@neutrinojs/loader-merge');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move this require to be above the web one.


const MODULES = path.join(__dirname, 'node_modules');

module.exports = (neutrino, options) => {
neutrino.use(web, options);
neutrino.options.extensions = ['vue']; // eslint-disable-line no-param-reassign
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now have vue in the extensions list, so we don't need this line now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I include the js extension, I get an error:

ERROR in   Error: The loader "/Users/haali/Documents/Mozilla/projects/neutrino-dev/node_modules/html-webpack-plugin/lib/loader.js!/Use  rs/haali/Documents/Mozilla/projects/neutrino-dev/node_modules/html-webpack-template/index.ejs" didn't return html.

If extensions is something like ['jsx', 'vue', 'ts', 'mjs' ] (without the js extension), then it works. Do you know why this is happening?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, should we include .json in the list of extensions? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this:

jantimon/html-webpack-plugin#602

Basically, I think the vue-loader is only supposed to accept vue extensions:

neutrino.config.module
    .rule('vue')
      .test(/\.vue$/) // instead of .test(neutrino.regexFromExtensions())
      .use('vue')
        .loader(require.resolve('vue-loader'))
        .options(options);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, should we include .json in the list of extensions? :)

I...don't know. Give it a shot and see if the tests continue to pass, but I'm not sure what effect it may have.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I figured, sounds good!

Btw, instead of hardcoding /\.vue$/, we could just do neutrino.regexFromExtensions(['vue']).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍


neutrino.config.module
.rule('vue')
.test(neutrino.regexFromExtensions())
.use('vue')
.loader(require.resolve('vue-loader'))
.options(options);

neutrino.config.when(neutrino.config.module.rules.has('lint'), () => {
neutrino.use(loaderMerge('lint', 'eslint'), {
plugins: ['vue'],
envs: ['node'],
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
rules: {
'vue/jsx-uses-vars': 2
}
});
});

if (neutrino.config.plugins.has('stylelint')) {
neutrino.config.plugin('stylelint')
.tap(([options, ...args]) => [
merge(options, {
files: ['**/*.vue'],
config: {
processors: [require.resolve('stylelint-processor-html')],
rules: {
// allows empty <style> in vue components
'no-empty-source': null
}
}
}),
...args
]);
}

neutrino.config
.resolve
.modules.add(MODULES).end()
.extensions.add('.vue').end()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this automatic from the web preset:

neutrino.config.resolve.extensions.merge(neutrino.options.extensions.map(ext => `.${ext}`);

Something like that.

.end()
.resolveLoader.modules.add(MODULES);
};
31 changes: 31 additions & 0 deletions packages/vue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@neutrinojs/vue",
"version": "7.3.2",
"description": "Neutrino preset adding Vue.js support.",
"main": "src/index.js",
"author": "Capi Etheriel <barraponto@gmail.com> (https://barraponto.blog.br)",
"license": "MPL-2.0",
"peerDependencies": {
"neutrino": "^7.0.0"
},
"devDependencies": {
"neutrino": "^7.2.1"
},
"keywords": [
"neutrino",
"neutrino-preset",
"vue",
"web",
"hot module replacement"
],
"dependencies": {
"@neutrinojs/web": "^7.3.2",
"@neutrinojs/loader-merge": "^7.3.2",
"babel-preset-vue": "^1.2.1",
"deepmerge": "^1.5.2",
"eslint-plugin-vue": "^2.1.0",
"stylelint-processor-html": "^1.0.0",
"vue-loader": "^13.5.0",
"vue-template-compiler": "^2.5.6"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a peerDependency of something? I don't see this used anywhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
Loading