How to dynamically import markdown files - javascript

I'm building a blog using Vue, where I'd like my posts to be written as markdown files.
Currently I have the following structure
src/
posts/
blogpost1.md
blogpost2.md
view/
myComponent.vue
I want myComponent.vue to dynamically load a markdown post based on the route params.
For instance if we visited .../blog/blogpost2, then we would dynamically load in blogpost2.md
My current implementation is as follows:
<template>
<div
v-html="md"
></div>
</template>
<script>
import marked from "marked";
export default {
data () {
return {
md: undefined,
};
},
created () {
const importRoute = `posts/blog/${this.$route.params.postName}.md`;
const md = require(importRoute);
this.md = marked(md);
},
};
</script>
To load the markdown file I'm using markdown-loader in webpack.
I'm also using copy-webpack-plugin to try to copy my entire /posts dir into my build folder.
configureWebpack: {
module: {
rules: [{
test: /\.md$/,
use: [
{ loader: "html-loader", options: {} },
{ loader: "markdown-loader", options: {} },
],
},
],
},
plugins: [
new CopyPlugin({
patterns: [
{ from: "src/posts", to: "posts" },
],
}),
],
},
When I run npm run serve however, I receive
[Vue warn]: Error in created hook: "Error: Cannot find module './posts/blogpost2.md'"
And when I look at the pages in my browser I don't see the posts directory (see image below)
However, when running npm run build:staging I do see the posts directory in /dist
Why am I unable to import import blogpost2.mddynamically?

My attempt at using a dynamic import that changes based on the route will not work because webpack is unable to properly bundle the imported files. This is because webpack needs to know what it will be importing in order to bundle them correctly.
Instead we can use a different approach of copying our markdown directory as it is into /dist and sending a request to fetch each markdown file.
We end up with this in our webpack config (or in my case vue.config.js):
const CopyPlugin = require("copy-webpack-plugin");
configureWebpack: {
plugins: [
new CopyPlugin({
patterns: [
{ from: "src/posts", to: "posts" },
],
}),
],
},
Note that we don't need the webpack loaders shown in my initial question because webpack itself isn't handling the loading of these files.
This ensures that posts/ gets copied as it is when we build this project.
Then in our component we can dynamically retrieve Markdown files like so:
<template>
<div
v-html="md"
></div>
</template>
<script>
import marked from "marked";
export default {
data () {
return {
md: undefined,
};
},
async created () {
const res = await fetch(`http://localhost:8081/posts/${this.$route.params.postName}.md`);
const md = await res.text();
this.md = marked(md);
},
};
</script>

// I once need to do a similar thing and faced similar challenges but it was built with Create React App, so please take my answer with a pinch of salt.
Try placing your import path inside require(…) (or import(…), if you ever use it) instead of declaring a variable then passing it in. I remember WebPack has trouble finding files when the paths are not static strings (hard-coded in this way). Whatever dynamic path it may be, your path needs to start with a static string, but you can concatenate dynamic values behind it.
Also, notice the ./ I added to the front of the path. Ignore it if you're using absolute imports, otherwise, this might also be a reason why the code failed?
created () {
// Instead of:
// const importRoute = `posts/blog/${this.$route.params.postName}.md`;
// const md = require(importRoute);
// Try:
const md = require(
`./posts/blog/${this.$route.params.postName}.md`
);
this.md = marked(md);
},
Extra: As much as I know, that the created hook in Vue is supposed only be synchronous, but after stumbling upon this answer I decided to include this snippet too. This one uses dynamic import. If all else fails perhaps you can give this a try?
async created () {
const { default: url } = await import(
`posts/blog/${this.$route.params.postName}.md`
);
const res = await fetch(url);
const md = res.text();
this.md = marked(md);
},

Related

Contentful with Nuxt.JS "Expected parameter accessToken"

I made a page which pulls data from Contentful. The data is pulling correctly, but buttons which use functions from methods don't work. Live updating of variables (for example, using v-model) doesn't work either.
I see this error in the console:
I think this error is the problem. Does anyone know what's wrong? I have no clue how to solve it :(
My contentful.js:
const contentful = require('contentful')
const client = contentful.createClient({
space: process.env.CONTENTFUL_ENV_SPACE_ID,
accessToken: process.env.CONTENTFUL_ENV_ACCESS_TOKEN
})
module.exports = client
Code which pulls data:
export default {
layout: "landing_page",
asyncData() {
return client
.getEntries({
content_type: "landingPage"
})
.then(entries => {
return { contentfulData: entries.items[0].fields };
});
},
computed: {
styles() {
return landingPageCss;
}
},
components: {
priceBox,
contact,
home,
aboutUs,
footerDiv
}
};
The best approach is used dotenv package to that. Set your env keys in .env file.
nuxt.config.js file should contain:
const env = require('dotenv').config()
export default {
mode: 'universal',
...
env: env.parsed,
...
}
Look at this video: https://codecourse.com/watch/using-env-files-with-nuxt
If you use dotenv you need to do following steps:
npm install --save-dev #nuxtjs/dotenv
Then you install it as an module. Note here if you using Nuxt.js older then v2.9 then you ahve to go to nuxt.config.js and put your code into the module section:
...
module: [
'#nuxtjs/dotenv'
]
...
If there is no module section then create one.
If you using newer then v2.9 then you put it into the buildModules
...
buildModules: [
'#nuxtjs/dotenv'
]
...
Your variables that are saved in the .env file are now accessable through context.env or process.env

React : setting background image for a file present on the server

I want to use a background image for my website built using React.
I include in the .scss file :
html {
background-color: #FFFCB2;
background-image: url(https://mdn.mozillademos.org/files/11991/startransparent.gif);
}
and I just import the .scss file in my index.jsx main page :
import "styles/index.scss"
And it works perfectly
But when I use the same image saved on my folder, like this :
html {
background-color: #FFFCB2;
background-image: url(../static/images/startransparent.gif);
}
I obtain the following error message in my web client :
./styles/index.scss
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
ModuleParseError: Module parse failed: Unexpected character '' (1:6)
You may need an appropriate loader to handle this file type.
(Source code omitted for this binary file)
Most of the references to this error message (like in this post) refer to including a series of instructions in my webpack.config.
However I have no webpack.config in my main architecture, I am using babel (I'm not sure how these two facts are connected, I am not proficient in JS). So I wouldn't know where to apply this.
How to make images load properly from local? What if I don't have a webpack.config file in my project?
**Edit : It seems my lack of webpack.config comes from the fact that I'm using next. **
My current next.config.js file :
const withTranspileModules = require("next-transpile-modules")
const withTypescript = require("#zeit/next-typescript")
const withSass = require("#zeit/next-sass")
const _ = require("lodash")
const config = {
exportPathMap() {
return {
"/": { page: "/" },
}
},
distDir: "build",
transpileModules: ["#iconify/react"],
}
module.exports = _.flow([,withTranspileModules, withTypescript, withSass])(config)
Please add this to your webpack configuration file . It can solve gif loader problem. Please find this link for more reference
{
test: /\.(ttf|eot|svg|gif)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
include: SRC,
use: [{
loader: 'file-loader'
}]
}]
},
The problem originated from the fact that I was using next and not webpack, which makes it somehow easier to install the loader, but less documented on the Internet.
I installed the "next-images" library, and changed my next.config.js to the following :
const withTranspileModules = require("next-transpile-modules")
const withTypescript = require("#zeit/next-typescript")
const withSass = require("#zeit/next-sass")
const withImages = require('next-images');
const _ = require("lodash")
const config = {
exportPathMap() {
return {
"/": { page: "/" },
}
},
distDir: "build",
transpileModules: ["#iconify/react"],
}
module.exports = _.flow([withTranspileModules, withTypescript, withSass,withImages])(config)
I was inspired by the solution appearing in this github issue

Compile template files/stubs into JS variables with Laravel mix

I have a folder /resources/js/stubs. In that folder sits a few files, lets say User.stub, Controller.stub and Migration.stub. I would like to use the content of these files in my javascript by doing something like this
import Templates from './Templates.js'
console.log(Templates.User)
// The content of User.stub is printed
I don't want to use this approach
module.exports = name => `
...
`
I could also use the backend to load the files into the view but I would prefer not to.
So then that requires preprocessing. Can I do it with Laravel mix somehow? If not what are my options, what do I need to add to my Laravel app?
partial solution
Thanks to Diogo Sgrillo for pointing out this solution in the comment.
Install raw-loader
yarn add raw-loader --dev
webpack.mix.js
Add this configuration (In my case all the files will be named with a .stub extension.):
mix.webpackConfig({
module: {
rules: [
{
test: /\.stub$/i,
use: 'raw-loader',
},
],
},
});
Also add a separate pipe in webpack.mix.js like this:
mix.js('src/templates/index.js', 'src/templates.js')
It will compile a list of templates in index.js file and put them in templates.js.
src/templates/index.js
// Dynamically load all stubs using raw-loader (see webpack.mix.js)
let stubs = require.context('./', true, /\.stub$/i);
// Create a object with the filename as key and content as value
exports.stubs = stubs.keys().reduce((result, key) => {
return {
[key.replace(/\.\//,'').replace(/\.stub$/,'')] : stubs(key).default,
...result
}
}, {});
export default { /* dont remove this export default or it will break !?? */}
Later it can be used like this:
let templates = require('./templates.js')
console.log(templates['User.php'])
Please comment or add another answer on how to do this more smooth. What is the difference between exports/export? I cant use import with this method, only require and it breaks if I try to export default (or remove export default.

Render multiple pages unrelated to the main app with Webpack and Mustache

I'm developing a Chrome Extension and I use Webpack to bundle it. I've got my compiled bundle, which is the main part of the app, but I also need an options page to describe the functionality. This options page has nothing to do with the bundle, it's just a static HTML file.
I must put a lot of things in that options page so I want to render that page with Mustache and define all content with JavaScript. For the most part, I've done that.
Here's my Webpack config (I've removed the parts regarding my app):
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
output: {
path: path.join(__dirname, 'extension/build/')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/options/index.html',
inject: false
})
],
module: {
rules: [
{
test: /\.html$/,
loader: 'mustache-loader',
options: {
render: require('./src/options/index.js')
}
}
]
}
}
and in my src/index.js, I have:
require('./options/index.html')
This will open the template and render it with the data in src/options/index.js.
There's a problem with that, however. I run Webpack with webpack --watch and changes to index.js (it holds the template data) do not trigger a rebuild. Also, I would need to go through a lot of trouble to create another static HTML file in the same manner.
It would be ideal if HtmlWebpackPlugin automatically used the template I require() in my entry point so that I don't need to explicitly set it. Also, it would be great if it automatically used a js in that same location to get the data. For example:
require('./options/index.html`)
Renders the template with data from ./options/index.html.js and then emits it. It would be even better if it emitted it to a custom folder specified in the Webpack config.
Is that possible? I couldn't find a plugin/loader that does that.
Edit: I was able to partly fix the rebuild problem by specifying the render option as a function:
{
test: /\.html$/,
loader: 'mustache-loader',
options: {
render () {
var file = './src/options/index.js'
delete require.cache[require.resolve(file)]
return require(file)
}
}
}
But it still doesn't work properly. The rebuild would only trigger after I make changes to index.html. This means that if I change index.js, I need to go and save index.html as well to trigger the build.

Webpack: How can I create a loader for "webpack" which takes an array of dependencies?

For example, I use AMD definition in my project, and use "webpack" for project building. It's possible to create some loader which will take a dependencies in array format?
define(
[
'mySuperLoader![./path/dependency-1, ./path/dependency-2, ...]'
],
function() {
// ... some logic here
}
)
Project example: gitHub
If you want to port the load-plugin's behavior to webpack, you need to do this:
1. Create a custom resolver
This is because mySuperLoader![./path/dependency-1, ./path/dependency-2, ...] does not point to a single file. When webpack tries to load a file, it first:
resolves the file path
loads the file content
matches and resolves all loaders
passes the file content to the loader chain
Since [./path/dependency-1, ./path/dependency-2, ...] is not a proper file path, there is some work to do. It is even not a proper JSON.
So, our first goal is to turn this into mySuperLoader!some/random/file?["./path/dependency-1", "./path/dependency-2", ...]. This is usually done by creating a custom resolver:
// webpack.config.js
var customResolverPlugin = {
apply: function (resolver) {
resolver.plugin("resolve", function (context, request) {
const matchLoadRequest = /^\[(.+)]$/.exec(request.path);
if (matchLoadRequest) {
request.query = '?' + JSON.stringify(
matchLoadRequest[1]
.split(", ")
);
request.path = __filename;
}
});
}
};
module.exports = {
...
plugins: [
{
apply: function (compiler) {
compiler.resolvers.normal.apply(customResolverPlugin);
}
}
]
};
Notice request.path = __filename;? We just need to give webpack an existing file so that it does not throw an error. We will generate all the content anyway. Probably not the most elegant solution, but it works.
2. Create our own load-loader (yeah!)
// loadLoader.js
const path = require("path");
function loadLoader() {
return JSON.parse(this.request.match(/\?(.+?)$/)[1])
.map(module =>
`exports['${path.basename(module, '.js')}'] = require('${module}');`
)
.join('\n');
}
module.exports = loadLoader;
This loader parses the request's query we have re-written with our custom resolver and creates a CommonJS module that looks like this
exports['dependency-1'] = require('path/to/dependency-1');
exports['dependency-2'] = require('path/to/dependency-2');
3. Alias our own load-loader
// webpack.config.js
...
resolveLoader: {
alias: {
load: require.resolve('./loadLoader.js')
}
},
4. Configure root
Since /path/to/dependency-1 is root-relative, we need to add the root to the webpack config
// webpack.config.js
resolve: {
root: '/absolute/path/to/root' // usually just __dirname
},
This is neither a beautiful nor an ideal solution, but should work as a makeshift until you've ported your modules.
I don't think that you should use a loader for that. Why don't you just write:
require("./path/dependency-1");
require("./path/dependency-2");
require("./path/dependency-3");
It accomplishes the same thing, is much more expressive and requires no extra code/loader/hack/configuration.
If you're still not satisfied, you might be interested in webpack contexts which allow you to require a bulk of files that match a given filter. So, if you write
require("./template/" + name + ".jade");
webpack includes all modules that could be accessed by this expression without accessing parent directories. It's basically the same like writing
require("./table.jade");
require("./table-row.jade");
require("./directory/folder.jade")
You can also create contexts manually like this
var myRequire = require.context(
"./template", // search inside this directory
false, // false excludes sub-directories
/\.jade$/ // use this regex to filter files
);
var table = myRequire("./table.jade");

Categories

Resources