https://www.red-gate.com/simple-talk/dotnet/net-development/introduction-to-vue-js-with-a-single-page-application-spa-in-visual-studio/
Main.js
import Vue from 'vue';
import App from './App.vue';
import Router from './router';
new Vue({
el: '#app',
template: '<App/>',
components: { App },
router: Router
});
这是进入JavaScript应用程序的入口点,所以当设置webpack以创建应用程序包时,您将指向这个文件。
前三行导入设置应用程序所需的模块。Vue是Vue.js库,它是在packages.json文件中建立的devDependencies之一,并通过运行npm安装下拉到开发环境中。接下来是在App.vue中定义的App组件。最后,Router导入在router.js文件中定义的路由信息,它让Vue知道在导航到不同的URL时要显示什么。
在导入部分之后,可以看到一个新的Vue实例被初始化,然后告诉它绑定到index.html页面中定义的#app元素。Vue构造函数对象的模板和组件属性告诉Vue,您希望用App.vue中定义的App组件填充index.html中的#app元素。最后,路由器属性允许将router.js中定义的路由信息传递给Vue实例。
Router.js
该文件用于初始化Vue路由器,并定义以什么URL显示哪些组件。
import Vue from 'vue';
import VueRouter from 'vue-router';
import PageA from './Pages/PageA.vue';
import PageB from './Pages/PageB.vue';
const routes = [
{ path: '/', component: PageA },
{ path: '/pagea', component: PageA },
{ path: '/pageb', component: PageB },
]
Vue.use(VueRouter);
const router = new VueRouter({ mode: 'history', routes: routes });
export default router;
再次从导入开始。这次导入Vue库。Vue Router库,用于管理Vue的路由选择(同时也是package.json中的devDepende.)以及两个PageA和PageB(分别从PageA.vue和PageB.vue导入)的Vue组件。接下来是定义URL和显示哪个组件的路由数组,然后调用Vue.use(VueRouter),它初始化Vue中的路由。之后,您创建一个新的VueRouter实例,并传入定义的路由,以及告诉路由器如何管理页面状态信息(在本例中,我使用“history”模式)。
然后返回初始化的VueRouter实例作为模块的默认导出。
App.vue
这是应用程序中的顶级组件。在应用程序中,它是一个非常重要的组件:
<template>
<div>
Hello from App Vue: <strong>{{</strong> message <strong>}}</strong>
<router-link to="/pagea">Go to A</router-link>
<router-link to="/pageb">Go to B</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Message from App.vue',
};
}
};
</script>
组件在页面上呈现时显示来自App.vue的message内容。它还有一个<router-view>组件,其中将显示与路由器中的URL相关联的组件。为了演示该功能,有两个<router-link>组件,允许用户从PageA切换到PageB。您可以从Vue路由器页面了解有关Vue中路由的更多细节。
PageA.vue / PageB.vue
页面组件也是非常简单的组件,它们可以演示路由是否正常工作。
<template>
<div>
Page A - <strong>{{</strong> message <strong>}}</strong>
</div>
</template>
<script>
export default {
name: 'PageA',
data() {
return {
message: 'Data from Page A'
};
}
};
</script>
这个内容可以复制到PageA.vue和PageB.vue中,但是显然您想要更新PageB.vue中的任何文本,比如PageB。与App.vue类似,此组件仅显示一条消息,指示文本的定义位置。
What is Webpack?
Webpack是一个模块捆绑器带有用于将转换应用到应用程序代码的管道。要使用它,需要在应用程序中指定一个或多个入口点——在本例中,唯一的入口点是Main.js文件。Webpack然后读取该文件并定位其中引用的任何JavaScript模块。然后,它递归地读取这些模块并定位它们所依赖的任何JavaScript模块,等等,直到它具有应用程序中使用的所有代码的图。然后,它将来自所有那些模块的所有代码捆绑到一个包含所有代码的捆绑包中(有一些选项可以将捆绑包分割成多个文件,我在这里将不深入讨论)。在本例中,这看起来类似于:
大多数浏览器都支持ES6和JavaScript模块的概念,因此在构建现代JavaScript应用程序时,不必使用bundler。
单文件Vue组件支持。开发人员倾向于真正理解.vue文件如何允许您为给定组件定义HTML、脚本和CSS。但是,JavaScript不能原生地解释.vue文件。Webpack包含一个加载程序,该加载程序读取.vue文件,将其分解为其组成部分,然后将其打包为可以像正常那样引用的JavaScript模块。
ES5支持。您可能希望使用最新的JavaScript语法编写应用程序。然而,当前版本的IE(在编写本文时)和许多较老版本的浏览器不支持ES6。Webpack允许您添加一个插件来将代码从ES6转换为ES5格式(您将使用Babel)。
优化。未优化的包初始化速度大约是相同未包的代码的三倍。优化的包初始化速度大约快4倍(来源)。对于较小的应用程序,差异可以忽略不计,但对于较大的应用程序,您将看到一些好处。
The Build Process
应用程序文件就绪后,您最终可以关注构建过程,该过程将如下所示:
NPM Script :
这是package.json文件中定义的脚本。它们用于设置NODE_ENV变量,以指示要运行的构建类型。
NODE_ENV:
这是一个环境变量,用于存储NPM脚本中定义的值。
config.js:
这是一个JavaScript模块,它导出包含根据NODE_ENV值而变化的webpack配置设置的JavaScript对象。
webpack.config.js
这是一个JavaScript模块,它使用config.js模块中的配置设置来设置和导出webpack配置对象。
Webpack
这是一个命令行实用程序,它读取webpack配置并根据这些设置打包应用程序。
注意:Node JS使用公共JS模块语法,而不是ES6模块语法。这意味着模块导入和导出语法在这里与Vue应用程序中稍有不同。
Config.js
如前所述,该文件是一个Common JS模块,包含可以根据NODE_ENV设置而变化的配置设置。尽管可以将这些配置设置直接放入webpack配置文件中,但是像这样将它们分离出来有一些优点。webpack配置趋向于更长和更复杂,因此这使文档大小更易于管理。其次,它提供了一个隔离,帮助人们避免更改他们应该远离的webpack中的配置设置。
const configuration = {
// Application settings that can be configured from one environment to the next. These
// are output to the app-settings.js file that is then referenced as a plugin within
// webpack so they can be changed manually if needed.
//可以从一个环境配置到下一个环境的应用程序设置。这些设置被输出到//app-settings.js文件,然后作为webpack中的插件引用,以便根据需//要手动更改它们。
appSettings: {
settingA: 5,
settingB: 10
},
// Identifies the type of build that has been requested. Primarily used to vary
// settings for different types of builds.
//标识已请求的构建类型。主要用于不同类型的构建的不同设置。
buildTarget: process.env.NODE_ENV || 'development',
// Specifies whether webpack should watch for changes on the file system and
// automatically repack bundles when changes occur.
//指定webpack是否应该监视文件系统的更改,以及当发生更改时,自动重新打包包。
watch: false,
// Specifies the webpack mode
webpackMode: 'development'
};
/*******************************************************************************
* Define watch-only settings
******************************************************************************/
if (configuration.buildTarget === 'watch') {
configuration.watch = true;
}
/*******************************************************************************
* Define production-only settings
******************************************************************************/
if (configuration.buildTarget === 'production') {
// Define production-only settings
configuration.webpackMode = 'production';
}
//Export the configuration
module.exports = configuration;
这个脚本首先创建一个对象并将其存储在配置变量中。这是最终将从这个模块导出的配置对象,并且为开发构建设置默认值。此配置中定义的appSettings用于特定于环境的设置,这些设置将呈现给从index.html页面引用的app-settings.js文件。流程对象在本地可用,并提供各种与流程相关的元数据,包括访问环境变量(通过env属性)。接下来,将watch属性设置为false,将webpackMode属性设置为development。
在配置对象定义之后,有两个if语句检查buildTarget值,'watch' or'production'。在if语句中,将更改配置对象以考虑此类构建所需的设置。如果buildTarget被设置为watch,那么请注意watch属性被翻转为true。如果buildTarget是生产,那么webpackMode被设置为生产。这里只有几个配置设置,因为我希望保持简单。您很可能会在您的webpack构建中遇到需要根据构建类型更改的场景,当这种情况发生时,只需在配置对象中为它们创建一个变量,在适当的if语句中相应地更新它们,然后在webpack配置文件中使用配置设置值。此外,要知道还可以使用环境特定值修改appSettings对象。
导出模块对于公共JS模块的工作方式与ES6模块稍有不同。在ES6中,使用导出关键字。对于Common JS,必须将导出的对象分配给module.export,这是您在脚本的最后一行看到的操作。
Webpack.config.js
最后,是时候添加驱动webpack进程的webpack配置文件了。与config.js文件一样,这是一个公共JS模块。该文件从packages.json文件中定义的npm脚本传递到webpack中。
const config = require('./config'); // Configuration settings 配置设置
const path = require('path'); // Path library used for building file locations 用于构建文件位置的路径库
const fileSave = require('file-save'); // Utility for writing files to disk 用于将文件写入磁盘的实用程序
const webpack = require('webpack'); // Webpack library
const VueLoaderPlugin = require('vue-loader/lib/plugin'); // Plugin used for loading single-component vue files 用于加载单个组件vue文件的插件。
// Output the application settings file 输出应用程序设置文件
if (config.appSettings != null) {
// NOTE: the replace regex on the next line removes the quotes from properties. It is rudimentary and can be removed 在下一行中 replace regex,会从属性中删除引号
// if it causes issues (because the quotes are technically OK I just think they look bad).
//如果它引起问题(因为在技术上还可以,我只是觉得它们看起来很糟糕)
var appSettingsOutput = JSON.stringify(config.appSettings, null, 4).replace(/\"([^(\")"]+)\":/g, "$1:");
fileSave(path.join(path.resolve(__dirname, "../../wwwroot/js"), "app-settings.js"))
.write("window.appSettings = " + appSettingsOutput);
}
const webpathConfig = {
mode: config.webpackMode, // Specifies whether to use built-in optimizations accordingly (options: production | development | none) 指定是否相应地使用内置优化
entry: "./App/main.js", // Specifies the entry point of the application where webpack begins the packaging process. 指定webpack开始打包过程的应用程序的入口点
output: {
path: path.resolve(__dirname, "../../wwwroot/js"), // Specifies the output directory for any wepback output files 指定任何wepback输出文件的输出目录
filename: "bundle.js", // Specifies the file name template for entry chunks (TODO: figure out what an entry point chunk is),指定条目块的文件名模板(找出入口点块是什么)
publicPath: "/js/" // Specifies the page-relative URL to prefix before assets to ensure they can be resolved by a browser. (Notice this value is injected into index.html to refer to the bundle.js file created by webpack).
//指定在资产之前前缀的页面相关URL,以确保可以通过浏览器解析它们。注意,这个值被注入index.html以引用由webpack创建的bundle.js文件
},
resolve: {
alias: {
vue: 'vue/dist/vue.js' // This is required to avoid the error 'You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.'
//为了避免“您正在使用模板编译器不可用的仅运行时生成的Vue”的错误,需要这样做。要么将模板预编译成渲染函数,要么使用编译器包含的构建。
}
},
module: {
rules: [
{ test: /\.vue$/, loader: 'vue-loader' }, // Specifies that files with the .vue extension should be processed by vue-loader which is what breaks up a single-file vue component into its constituent parts.
//指定具有.vue扩展名的文件应该由vue-loader处理,vue-loader将单个文件vue组件分解为其组成部分。
{ test: /\.js$/, loader: 'babel-loader', query: { presets: ['es2015'] } }, // Specifies that .js files should be run through the babel-loader for ES2015 to ES5 conversion.
//指定.js文件应该在ES2015到ES5转换的babel-loader中运行。
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] }, // Specifies that CSS should be included in the bundle from .CSS files as well as processed from the <style> section of vue single-file vue component.
//指定CSS应该包含在.CSS文件的包中,以及从vue单文件vue组件的<style>部分进行处理。
// ESLint rules
{
test: /\.(js|vue)$/,
exclude: /node_modules/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(__dirname, "App")],
options: {
eslintPath: path.join(__dirname, '../node_modules/eslint'),
fix: true,
formatter: require('eslint-friendly-formatter'),
emitError: false,
emitWarning: false,
failOnError: true,
failOnWarning: false
}
}
]
},
plugins: [
// Required per manual configuration section of the Vue Loader configuration instructions
// located at https://vue-loader.vuejs.org/guide/#vue-cli
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development') },
'appSettings': "window.appSettings"
}),
],
watch: config.watch, // Flag indicating whether webpack should monitor files and update bundles automatically with any changes
watchOptions: { // Specifies watch options for the watching mechanism.
ignored: ['node_modules'] // Specifies directories to ignore (optimization).
}
};
module.exports = webpathConfig;
这可能需要考虑很多,但我将把它分解成更易于管理的部分。
Imports
导入出现在文件的前几行。您将注意到,导入Common JS模块使用require方法,该方法具有标识所需模块的参数。然后,require方法的结果存储在一个变量中,该变量可以在脚本中使用。config变量存储具有配置设置的config.js模块。可以在此脚本中从config变量引用config.js在配置对象上定义的任何属性。path 变量存储对路径模块的引用,该路径模块提供对公共路径函数的访问。fileSave变量存储从文件保存模块返回的方法。到目前为止,一切都是普通对象,但是也可以从模块返回方法。您将在一分钟内使用此方法将文本写入文件。webpack变量存储对webpack模块的引用。在最后,VueLoaderPlugin 存储vue-loader插件他允许webpack处理.vue后缀的文件。
管理配置设置
下一节旨在帮助管理已编译应用程序的配置设置。如果在应用程序中定义配置设置,那么它们最终打包在bundle.js文件中。在生产构建中,捆绑包文件经常被优化、最小化,甚至可能被丑化。这使得在已编译的应用程序中定位和修改配置设置极其困难。虽然您可以更新设置并重新编译应用程序,但我发现大多数QA经理都喜欢只构建一次应用程序,因为它减少了构建中发生更改和引入bug的可能性。为了避免这种情况,您必须创建一个包含可以轻松更新的应用程序设置的文件,然后使该文件可用于已编译的应用程序中。
如果您还记得config.js文件中定义的配置对象,那么它具有一个appSettings属性,用于存储环境特定的配置设置。您只需要稍加修改就可以将这些设置写入文件(在本例中为app-settings.js)。
appSettings: {
settingA: 5,
settingB: 10
}
这就是应该写入app-settings.js文件的内容:
window.appSettings = {
settingA: 5,
settingB: 10
};
webpack 允许你定义你自己的插件,它引用了已经存在的变量。您基本上可以告诉webpack,当它遇到名为appSettings(或者任何您决定称之为appSettings)的东西时,它应该引用window.appSettings。这允许您在外部定义配置设置,但是仍然可以从打包代码中引用它们(稍后将看到这个配置)。惟一需要注意的是,必须在bundle.js之前加载app-settings.js,以便当捆绑代码运行时定义window.appSettings。
以下是来自上面示例的代码部分,用于写出文件:
// Output the application settings file
if (config.appSettings != null) {
// NOTE: the replace regex on the next line removes the quotes from properties. It is rudimentary and can be removed
// if it causes issues (because the quotes are technically OK I just think they look bad).
var appSettingsOutput = JSON.stringify(config.appSettings, null, 4).replace(/\"([^(\")"]+)\":/g, "$1:");
fileSave(path.join(path.resolve(__dirname, "../../wwwroot/js"), "app-settings.js"))
.write("window.appSettings = " + appSettingsOutput + ";");
}
这个脚本做的第一件事是检查appSettings是否被定义。如果没有,则跳过文件写入过程。如果有要写入的设置,则调用config.AppSettings上的JSON.stringify以获得appSettings对象的文本表示。不幸的是,JSON.stringify在对象的属性名周围加上引号。这在技术上没有什么问题,但是阅读起来并不容易。因此,此方法调用字符串上的.replace,并使用正则表达式从属性名中删除引号。如前所述,fileSave实际上是一个方法,您调用此方法并传入要写入的文件的名称。该文件名是通过调用path.resolve来获得到web应用程序中的wwwroot/js文件夹的完整路径,然后调用path.join来连接该路径和app-settings.js文件名来构造的。然后,它调用fileSave方法返回的文件保存实例的write,后者写出app-settings.js文件的内容。注意,您正在这个字符串中添加window.appSettings=。
Webpack Configuration Object
如果需要,Webpack配置可能会变得非常复杂,所有使其复杂化的文档都可以在Webpack网站上找到。我的目标是尽可能简单地实现这一点,同时确保Vue开发的所有主要组件都就位。因此,我完全希望您需要在这里和那里添加一些配置,以适应您自己的应用程序的具体情况。因此,我完全希望您需要在这里和那里添加一些配置,以适应您自己的应用程序的具体情况。所以,让我们开始研究这个最小配置。
您将看到的第一件事是模式(mode)属性。这可以设置为开发、生产或无。当没有值设置(例如null),默认值就是生产。此设置自动加载为开发或生产版本优化Webpack生成的某些Webpack插件。请注意,您使用config.webpackmode设置了模式属性值,因为它是由config.js文件控制的设置。接下来是entry属性。这将定义应用程序的Webpack入口点。有一个入口点main.js,字符串/app/main.js中.
output属性是一个包含Webpack输出设置的对象。在这个对象中,有三个属性被设置。
路径(path)–包含捆绑文件的写入路径。在本例中,将它们写入项目中的wwwroot/js。此值必须是绝对路径,因此您将使用path.resolve从相对路径获取绝对路径。
文件名(filename)–这是用于保存包的文件名。只生成了一个文件,所以只需将其命名为bundle.js。但是,如果您有多个入口点,您还可以在字符串中指定一些标记(名称、ID、哈希、Chunkash、ContentHash等),以保持名称的唯一性。
publicPath–这是输出目录的公共URL。这对于从服务器引用资源静态资源(如图像)很重要。由于项目中的wwwroot文件夹代表应用程序的根目录,并且您要将文件放在根目录下的JS文件夹中,因此请将该值用于公共路径。
在output属性之后,您将看到resolve属性,它控制如何解析模块。Webpack在大多数情况下都能很好地定义默认值,但是您会遇到需要修改它们的情况。其中一种情况是,Vue带有两个软件包。一个包含模板编译器,另一个不包含。默认情况下,Webpack引用的是没有模板编译器的版本,这会导致您在使用模板编译器不可用的仅运行时版本Vue时出错。要么将模板预编译为呈现函数,要么使用编译器包含的内部版本。为了解决这个问题,您需要引用不同版本的Vue。这是通过解析对象上的Alias属性实现的,该属性允许提供指向特定资源的名称(即别名)。
在这个情形下,别名的vue引用的是一个包含编译器的vue,它在node_modules目录的下vue/dist/vue.js。还可以使用Alias属性设置对应用程序中文件夹的命名引用。如果您有一个复杂的目录结构,并且有助于避免使用长相对路径引用文件,这些路径必须通过应用程序目录结构(例如…/../app/components/)进行回溯。
在resolve属性下是module属性,它控制Webpack中模块的行为。Webpack中的模块是什么?Webpack处理的任何文件都可以视为模块。一个javascript文件是一个模块,一个vue文件是一个模块。仅仅是css和图片也可以是模块。Webpack从您提供的入口点开始处理代码。当它在代码中遇到模块引用(例如,导入或需要javascript中的语句、CSS中的URL、HTML页面中的图像引用等)时,它确定是否可以打包该模块并执行此操作。然而,webpack本身并不了解每种文件类型——它要求开发人员创建名为loaders的扩展名,以帮助webpack了解如何打包不同类型的文件。例如,Webpack本身不支持.vue文件,但Vue加载器插入Webpack,使Webpack能够正确处理.vue文件。
加载程序是从模块对象中的Rules属性配置的。通常,规则标识处理模块时要使用的加载程序,并定义一个测试,以确定给定文件是否满足该加载程序处理所需的条件。配置包含四个规则,其中三个非常简单:
{ test: /\.vue$/, loader: 'vue-loader' }
第一个规则测试模块的文件名,如果以.vue结尾,则使用vue加载程序处理模块。如前所述,Vue加载程序允许Webpack处理.vue文件。字符串Vue加载器解析回从package.json文件下拉的Vue加载器包。
{ test: /\.js$/, loader: 'babel-loader', options: { presets: ['es2015'] } }
第二个规则测试模块的文件名,如果以.js结尾,则使用babel加载程序来处理该文件。这个加载程序负责将ES6/ES2015代码转成大多数浏览器支持的ES5语法中。您将注意到此规则上有一个选项属性。option属性允许您将特定于加载器的选项传递给加载器。在这种情况下,babel加载程序应该使用ES2015规则预设来生成代码。字符串babel加载器解析回package.json文件中下拉的babel加载器包。并且ES2015预设是可用的,因为packages.json文件中的“babel-preset-es2015”包。
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] }
第三个规则测试模块的文件名,如果以.css结尾,则使用“vue样式加载程序”和“css加载程序”加载程序来处理文件。虽然没有在这个项目中使用,但是这些提供了打包在单个文件Vue组件中定义的CSS文件和样式的能力。use属性接受加载程序数组,并可用于处理具有多个加载程序的单个模块。
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(__dirname, "App")],
options: {
eslintPath: path.join(__dirname, '../node_modules/eslint'),
formatter: require('eslint-friendly-formatter'),
emitWarning: true,
failOnError: true
}
}
第四个也是最后一个规则测试模块的文件名,如果它以.js或.vue结尾,则使用eslint加载程序来处理该文件。这是一个检查JavaScript代码语法错误的加载程序,因此您可以预先了解它们,而不是在运行时了解它们。这个规则上有一些额外的属性需要讨论,其中第一个属性是当加载程序执行顺序时管理的强制属性。值pre确保eslint在任何其他加载程序处理代码之前在代码上运行。接下来是include属性,它允许您指定一个路径数组,指示要由加载程序处理哪些文件。Webpack需要完整路径,因此使用path.join从相对路径构建完整路径。或者,如果要排除某些文件,可以使用“排除”属性。
option属性包含一个eslint-loader配置设置,eslintPath指定这个loader执行linting使用eslint模块的位置。
格式化(formatter )程序属性允许您指定一个格式化程序,用于输出由eslint标识的错误。在你使用这个配置,你使用的是“eslint-friendly-formatter”变量来自package.json指定的eslint-friendly-formatter的包。接下来是emitWarning设置,您将要启用它。这可以确保在运行Watch Build时输出警告,因此,如果将错误代码写入Webpack正在监视的文件,则至少会出现一些消息。.最后,您需要启用failonerror属性,以确保在遇到错误时停止生成。
Webpack配置对象的下一个特性是允许自定义Webpack构建过程的插件属性。Webpack有两个插件,第一个插件是:
new VueLoaderPlugin()
下一个插件是:
new webpack.DefinePlugin({
'appSettings': "window.appSettings"
})
webpack允许您使用defineplugin方法定义全局常量,这里使用defineplugin方法定义常量appsettings。您可以将这些常量设置为任何有效的javascript表达式,在本例中,将其设置为window.appsettings。这意味着无论何时在应用程序中引用appSettings,它最终都会实际引用window.appSettings中存储的数据。您可能记得window.app settings是在app-settings.js文件中定义的。它包含的配置设置必须能够在不重新编译应用程序的情况下进行修改。这是在外部文件和编译的代码库之间架起桥梁的机制。
最后,您拥有Watch和WatchOptions属性。Watch属性是一个布尔标记,用于指定是否希望WebPack监视生成的源文件,并在任何源文件更改时自动重新打包。在这里,将值设置为config.watch,该值由config.js文件中的配置代码管理。watchOptions属性包含允许您控制如何监视源文件的各个方面的设置。您可以设置轮询更改的频率,以及发现更改后等待编译的时间。在这种情况下,您将希望使用ignored属性来避免监视node_modules文件夹,因为它不应该经常更改,而且可能非常大。