Artigo original: How to create a Vue.js app using Single-File Components, without the CLI.

Uma compreensão dos componentes de arquivo único (em inglês, single-file components – SFCs) do Vue e do gerenciador de pacotes do Node (Node Package Manager – NPM) será útil para este artigo.

A interface de linha de comando, ou CLI, de um framework é o método preferido para estruturar um projeto. Ela fornece um ponto de partida para arquivos, pastas e configurações. Essa estrutura básica também inclui um processo de desenvolvimento e um de construção (do inglês, build). O processo de desenvolvimento permite ver as atualizações à medida que você edita o projeto. Já o processo de construção cria a versão final dos arquivos a serem utilizados em produção.

A instalação e a execução do Vue.js ("Vue") podem ser feitas através de uma tag de script que aponta para a rede de entrega de conteúdo (do inglês, content delivery network, ou CDN) do Vue. Nenhum processo de construção ou de desenvolvimento é necessário neste caso. No entanto, se você utilizar componentes de arquivo único (os single-file components - ou SFCs), será necessário converter esses arquivos em algo que o navegador possa entender. Os arquivos precisam ser convertidos em linguagem de hipertexto (HTML), estilo em cascata (CSS) e JavaScript (JS). Nesse caso, é necessário utilizar os processos de desenvolvimento e de construção.

Em vez de depender da CLI do Vue para criar a estrutura do projeto e fornecer um processo de desenvolvimento e de construção, construiremos um projeto do zero. Criaremos nossos próprios processos de desenvolvimento e de construção usando o Webpack.

O que é o Webpack?

O Webpack é um module bundler (empacotador de módulos, em português). Ele combina o código de vários arquivos em um só. Antes do Webpack, a pessoa usuária precisava incluir uma tag de script para cada arquivo JavaScript. Embora os navegadores estejam gradualmente oferecendo suporte a módulos do ES6, o Webpack continua sendo a maneira preferida de construir um código modular.

Além de ser um "module bundler", o Webpack também pode transformar o código. Por exemplo, ele pode pegar código JavaScript moderno (do ECMAScript 6 e posteriores) e convertê-lo em ECMAScript 5. Enquanto o Webpack empacota o código em si, ele transforma o código usando loaders e plug-ins. Pense nos loaders e plug-ins como complementos para o Webpack.

Webpack e Vue

Os componentes de arquivo único nos permitem construir um componente inteiro (estrutura, estilo e função) em um único arquivo. Além disso, a maioria dos editores de código fornece destaque de sintaxe e verificação de código para esses SFCs.

1_anBAz9QzClNtmAxp4ujdGA
Componente de arquivo único do Vue: perceba a extensão, .vue (documentação em inglês).

Observe que o arquivo termina com .vue. O navegador não sabe o que fazer com essa extensão. O Webpack, por meio do uso de "loaders" e de "plug-ins", transforma esse arquivo em HTML, CSS e JS que o navegador pode entender e consumir.

O projeto: criar uma aplicação do Vue do tipo "Hello World" usando componentes de arquivo único

Primeiro passo: criar a estrutura do projeto

O projeto básico do Vue incluirá um arquivo HTML, um arquivo JavaScript e um arquivo Vue (o arquivo com a extensão .vue). Vamos colocar esses arquivos em uma pasta chamada src. A pasta src nos ajudará a separar o código que estamos escrevendo do código que o Webpack criará posteriormente.

Como estaremos usando o Webpack, precisamos de um arquivo de configuração do Webpack.

Além disso, usaremos um compilador chamado Babel. O Babel nos permite escrever código em ES6 que será compilado para ES5. O Babel é um dos "recursos adicionais" para o Webpack. Ele também precisa de um arquivo de configuração.

Por fim, como estamos usando o NPM, também teremos uma pasta node_modules e um arquivo package.json. Eles serão criados automaticamente quando inicializarmos nosso projeto como um projeto do NPM e quando começarmos a instalar nossas dependências.

Para começar, crie uma pasta chamada hello-world. No terminal, vá para esse diretório e execute o comando npm init. Siga as instruções na tela para criar o projeto. Em seguida, crie as outras pastas (exceto a pasta node_modules) conforme descrito acima. A estrutura do seu projeto deve ficar assim:

1_jLNggGBoQ6A6xnqVyltFEA
A estrutura de projeto com SFCs do Vue mais simples.

Segundo passo: instale as dependências

Aqui está um resumo rápido das dependências que estamos usando:

vue: o framework do JavaScript

vue-loader e vue-template-compiler: usados para converter nossos arquivos do Vue em JavaScript.

webpack: a ferramenta que nos permitirá transformar o nosso código e agrupá-lo em um único arquivo.

webpack-cli: necessário para executar os comandos do Webpack.

webpack-dev-server: embora não seja necessário para nosso pequeno projeto (já que não faremos nenhuma solicitação HTTP), ainda "serviremos" nosso projeto a partir de um servidor de desenvolvimento.

babel-loader: transforma nosso código ES6 em ES5 (ele precisará da ajuda das duas próximas dependências).

@babel/core e @babel/preset-env: o Babel por si só não fará nada com o seu código. Esses dois "add-ons" nos permitirão transformar nosso código ES6 em código ES5.

css-loader: pega o CSS que escrevemos em nossos arquivos .vue ou qualquer CSS que possamos importar em nossos arquivos JavaScript e resolve o caminho para esses arquivos. Em outras palavras, ele descobre onde está o CSS. Este é outro loader que, por si só, não fará muito. Precisamos do próximo loader para fazer algo com o CSS.

vue-style-loader: pega o CSS que obtivemos do nosso css-loader e o injeta em nosso arquivo HTML. Isso criará e injetará uma tag de estilo no cabeçalho de nosso documento HTML.

html-webpack-plugin: pega nosso index.html e injeta nosso arquivo JavaScript agrupado no cabeçalho. Em seguida, copia este arquivo para a pasta dist.

rimraf: permite-nos, a partir da linha de comando, excluir arquivos. Isso será útil quando construirmos nosso projeto várias vezes. Usaremos isso para excluir quaisquer compilações antigas.

Vamos instalar essas dependências agora. No terminal, execute o seguinte comando:

npm install vue vue-loader vue-template-compiler webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env css-loader vue-style-loader html-webpack-plugin rimraf -D

Observação: o "-D" no final de cada dependência marca cada uma delas como uma dependência de desenvolvimento em nosso package.json. Estamos agrupando todas as dependências em um único arquivo. Portanto, para nosso pequeno projeto, não temos dependências de produção.

Terceiro passo: criar os arquivos (exceto o arquivo de configuração do Webpack)

<template>
  <div id="app">
    {{ message }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello World',
    };
  },
};
</script>

<style>
#app {
  font-size: 18px;
  font-family: 'Roboto', sans-serif;
  color: blue;
}
</style>
a1-App.vue
<html>
  <head>
    <title>Vue Hello World</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
a1-index.html
import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App),
});
a1-main.js
module.exports = {
  presets: ['@babel/preset-env'],
}
a1-babelrc.js

Até esse ponto, nada deve parecer muito estranho. Mantive cada arquivo muito básico. Adicionei apenas CSS e JS mínimos para ver nosso fluxo de trabalho em ação.

Quarto passo: instruir o Webpack sobre o que fazer

Toda a configuração necessária para o Webpack está presente. Precisamos fazer duas coisas finais: dizer ao Webpack o que fazer e executar o Webpack.

Abaixo está o arquivo de configuração do Webpack (webpack.config.js). Crie o arquivo no diretório raiz do projeto. Vamos discutir linha por linha o que está ocorrendo:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  entry: './src/main.js',
  module: {
    rules: [
      { test: /\.js$/, use: 'babel-loader' },
      { test: /\.vue$/, use: 'vue-loader' },
      { test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
    new VueLoaderPlugin(),
  ]
};
a1-webpack.config.js

Linhas 1 e 2: estamos importando os dois plug-ins que usamos abaixo. Observe que nossos loaders normalmente não precisam ser importados, apenas nossos "plug-ins". Em nosso caso, o vue-loader (que usamos na linha 9) também precisa de um plug-in para funcionar (no entanto, o Babel, por exemplo, não precisa).

Linha 4: exportamos nossa configuração como um objeto. Isso nos dá acesso a ele quando executamos os comandos do Webpack.

Linha 5: este é o nosso módulo de entrada (entry). O Webpack precisa de um ponto de partida. Ele procura em nosso arquivo main.js e começa a percorrer nosso código a partir desse ponto.

Linhas 6 e 7: este é o objeto módulo. Aqui, passamos principalmente um array de regras. Cada regra informa ao Webpack como lidar com determinados arquivos. Então, enquanto o Webpack usa o ponto de entrada main.js para começar a percorrer nosso código, ele usa essas regras para transformar nosso código.

Linha 8 (regra): essa regra instrui o Webpack a usar o babel-loader em qualquer arquivo que termine com .js. Lembre-se de que o Babel transformará o ES6 e versões posteriores em ES5.

Linha 9 (regra): essa regra instrui o Webpack a usar o vue-loader (e a não esquecer do plug-in associado na linha 17) para transformar nossos arquivos .vue em JavaScript.

Linha 10 (regra): às vezes, queremos passar um arquivo por dois loaders. De maneira contraintuitiva, o Webpack passará o arquivo da direita para a esquerda em vez de da esquerda para a direita. Aqui estamos usando dois loaders e dizendo ao Webpack: "pegue meu CSS do meu arquivo Vue ou de quaisquer arquivos JavaScript (css-loader) e injete-o em meu HTML como uma tag de estilo (vue-style-loader).

Linhas 11 e 12: fecha nosso array de regras e o objeto de módulo.

Linha 13: cria um array de plug-ins. Aqui adicionaremos os dois plug-ins que precisamos.

Linhas: 14 a 16 (plug-in): o HtmlWebpackPlugin pega a localização do nosso arquivo "index.html" e adiciona nosso arquivo JavaScript agrupado a ele através de uma tag de script. Esse plug-in também copiará o arquivo HTML para nossa pasta de distribuição (dist) quando construirmos nosso projeto.

Linha 17 (plug-in): o VueLoaderPlugin trabalha com nosso vue-loader para analisar nossos arquivos .vue.

Linha 18: fecha o array de plug-ins.

Linha 19: fecha o objeto do Webpack que estamos exportando.

Quinto passo: configurar o arquivo package.json para que possamos executar o Webpack

Nossa configuração está completa, agora queremos ver nossa aplicação. Idealmente, à medida que fazemos alterações em nossa aplicação, o navegador deve ser atualizado automaticamente. Isso é possível com o webpack-dev-server.

Exclua o script de test em nosso arquivo package.json e substitua-o por um script para servir nossa aplicação:


{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "serve": "webpack-dev-server --mode development"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.1.6",
    "@babel/preset-env": "^7.1.6",
    "babel-loader": "^8.0.4",
    "css-loader": "^1.0.1",
    "html-webpack-plugin": "^3.2.0",
    "rimraf": "^2.6.2",
    "vue": "^2.5.17",
    "vue-loader": "^15.4.2",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.5.17",
    "webpack": "^4.26.0",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  },
  "dependencies": {}
}
a1-package.json

O nome desse comando é de sua escolha. Eu escolhi chamá-lo de serve já que estaremos servindo nossa aplicação.

A partir de nosso terminal ou linha de comando, podemos executar npm run serve e isso, por sua vez, executará webpack-dev-server --mode development.

O --mode development é o que chamamos de flag ou opção. Ainda não falamos sobre isso, mas, essencialmente, instrui o Webpack de que você está no modo de desenvolvimento. Também podemos passar --mode production, que é algo que faremos quando construirmos nosso projeto. Essas opções não são necessariamente obrigatórias para que o Webpack funcione. Sem elas, você receberá uma mensagem de aviso dizendo para fornecer um modo ao executar o Webpack.

Eu digo "necessariamente obrigatórias" porque o Webpack minimizará nosso código no modo de produção, mas não no modo de desenvolvimento. Portanto, não pense que esses comandos não fazem nada – eles fazem.

Vamos executar npm run serve e ver o que acontece.

Quando executamos npm run serve, obtemos algumas informações em nosso terminal. Se tudo correr bem, o que temos será o seguinte:

1_UNoqxigEpgvVZRjs2VqxTA

Se rolarmos um pouco para cima, veremos isto:

1_ye4_gCeGPXcwgPcGUQf_rg

Acesse seu navegador em http://localhost:8080. Você verá sua mensagem "Hello World" em azul na fonte Roboto.

1_kKYxmKJ_rTBzT7rOvjZtgg

Agora, vamos atualizar o projeto e mudar a mensagem para Hello Universe. Observe que a página é atualizada automaticamente. Isso é ótimo, não é? Você consegue pensar em uma desvantagem?

Vamos mudar um pouco a aplicação e incluir um campo de entrada (input) ao qual vincularemos uma variável (usando v-model). Vamos exibir o conteúdo da variável em uma tag <h2> abaixo do campo de entrada. Também atualizei a seção de estilo para estilizar a mensagem. Nosso arquivo App.vue deve ficar assim:

<template>
  <div id="app">
    <input
      v-model="message"
      type="text">
      <h2 class="message">{{ message }}</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello world!',
    };
  },
};
</script>

<style>
.message {
  font-size: 18px;
  font-family: 'Roboto', sans-serif;
  color: blue;
}
</style>
a11-App.vue

Quando servimos nossa aplicação, teremos um campo de entrada com a mensagem Hello World abaixo dele. O campo de entrada está vinculado à variável message. Então, conforme digitamos, alteramos o conteúdo da tag <h2>. Vá em frente, digite no campo de entrada para mudar o conteúdo da tag <h2>.

Agora, volte para o seu editor e, abaixo da tag <h2>, adicione o seguinte:

<h3>Some Other Message</h3> (Alguma outra mensagem)

Salve seu arquivo App.vue e observe o que acontece.

O h2 que acabamos de atualizar digitando no campo de entrada voltou para Hello World. Isso acontece porque o navegador atualiza de fato a página e recarrega a tag de script e o conteúdo da página. Em outras palavras, não conseguimos manter o estado de nossa aplicação. Isso pode não parecer um grande problema, mas à medida que você testa sua aplicação e adiciona dados a ela, será frustrante se seu aplicativo "reiniciar" toda vez. Felizmente, o Webpack nos oferece uma solução chamada Hot Module Replacement (em português, algo como "substituição rápida dos módulos").

O Hot Module Replacement é um plug-in fornecido pelo próprio Webpack. Até esse ponto, não usamos diretamente o objeto do Webpack em nosso arquivo de configuração. No entanto, agora vamos importar o Webpack para que possamos acessar o plug-in.

Além do plug-in, passaremos uma opção adicional para o Webpack, a opção devServer. Nessa opção, definiremos hot como true. Também faremos uma atualização opcional em nosso fluxo de trabalho de construção: vamos abrir automaticamente a janela do navegador quando executarmos npm run serve. Fazemos isso definindo open como true também dentro da opção devServer.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');

module.exports = {
  entry: './src/main.js',
  module: {
    rules: [
      { test: /\.js$/, use: 'babel-loader' },
      { test: /\.vue$/, use: 'vue-loader' },
      { test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
    ]
  },
  devServer: {
    open: true,
    hot: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
    new VueLoaderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
  ]
};
a11-webpack.config.js

Observe que importamos o Webpack para que pudéssemos acessar o hotModuleReplacementPlugin. Adicionamos esse plug-in ao array plugins e, em seguida, informamos ao Webpack para usá-lo com hot: true. Abrimos automaticamente a janela do navegador quando servimos a aplicação com open: true.

Execute npm run serve:

1_59LVTDT3pEk3RzQ7dodmvw

A janela do navegador deve abrir. Se você abrir as ferramentas de desenvolvedor, deve notar uma pequena alteração na saída. Agora, ela informa que o "hot module replacement" está habilitado. Digite no campo de entrada para alterar o conteúdo da tag <h2>. Em seguida, altere a tag <h3> para ler: One More Message (Mais uma mensagem).

Salve seu arquivo e observe o que acontece.

O navegador não é recarregado, mas nossa alteração na tag <h3> é refletida! A mensagem que digitamos no campo de entrada permanece, mas a h3 é atualizada. Isso permite que nossa aplicação mantenha seu estado enquanto a editamos.

Sexto passo: criar nosso projeto

Até agora, servimos nossa aplicação. O que podemos fazer, porém, quisermos construí-la para poder distribuí-la?

Se você percebeu, quando servimos nossa aplicação, nenhum arquivo é criado. O Webpack cria uma versão desses arquivos que só existe na memória temporária. Se quisermos distribuir nossa aplicação "Hello World" para nosso client, precisamos fazer a build (construir) do projeto.

Isso é muito simples. Assim como antes, criaremos um script em nosso arquivo package.json para dizer ao Webpack para construir nosso projeto. Usaremos webpack como o comando em vez de webpack-dev-server. Também passaremos a opção --mode production.

Usaremos o pacote rimraf primeiro para excluir quaisquer builds anteriores que possamos ter. Faremos isso simplesmente com o comando rimraf dist.

dist é a pasta que o Webpack criará automaticamente quando construir nosso projeto. “Dist” é uma abreviação de "distribution", ou seja, estamos "distribuindo" o código de nossa aplicação.

O comando rimraf dist está instruindo o pacote rimraf a excluir o diretório dist Certifique-se de não executar rimraf src por acidente!

O Webpack também oferece um plug-in que realiza esse processo de limpeza, chamado clean-webpack-plugin. Eu escolhi usar dist como um modo alternativo.

Nosso arquivo package.json deve ficar assim:

{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "clean": "rimraf dist",
    "build": "npm run clean && webpack --mode production",
    "serve": "webpack-dev-server --mode development"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.1.6",
    "@babel/preset-env": "^7.1.6",
    "babel-loader": "^8.0.4",
    "css-loader": "^1.0.1",
    "html-webpack-plugin": "^3.2.0",
    "rimraf": "^2.6.2",
    "vue": "^2.5.17",
    "vue-loader": "^15.4.2",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.5.17",
    "webpack": "^4.26.0",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  },
  "dependencies": {}
}
a11-package.json

Existem três coisas a serem observadas:

  1. Criei um script separado chamado clean para que possamos executá-lo independentemente do script build.
  2. npm run build chamará o script independente clean que criamos.
  3. Eu usei o operador && entre npm run clean e webpack. Essa instrução diz: "execute npm run clean primeiro, só então execute webpack".

Vamos executar o projeto.

npm run build

O Webpack criará um diretório dist e nosso código estará dentro dele. Como nosso código não faz solicitações HTTP, podemos simplesmente abrir nosso arquivo index.html no navegador e ele funcionará conforme o esperado.

Se tivéssemos um código fazendo solicitações HTTP, encontraríamos alguns erros de origem cruzada ao fazer essas solicitações. Precisaríamos executar esse projeto a partir de um servidor para que funcionasse.

Vamos examinar o index.html que o Webpack criou no navegador e no editor de código.

1_Y0hwAs2CRmCuBrn7h1pFUw
R

Se abrirmos no editor ou olharmos o código-fonte nas ferramentas de desenvolvimento, veremos que o Webpack injetou a tag de script. No editor, porém, não veremos os estilos porque a tag de estilo é injetada dinamicamente em tempo de execução com JavaScript!

Perceba, também, que nossas informações no console do desenvolvedor já não estão presentes. Isso ocorre porque passamos a flag --production para o Webpack.

Conclusão

Entender o processo de construção por trás dos frameworks que você utiliza ajudará você a compreender melhor o próprio framework. Então, reserve um tempo para tentar construir um projeto em Angular, React, ou outro projeto em Vue, sem utilizar as respectivas interfaces de linha de comando (CLIs), ou apenas crie um site básico de três arquivos (index.html, styles.css e app.js), mas utilize o Webpack para servir e executar uma versão de produção.

Agradeço pela leitura!

Woz